/*
 * SPDX-License-Identifier: MIT
 *
 * Copyright 2010 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
 * Copyright 2015-2018 Philippe Proulx <philippe.proulx@efficios.com>
 *
 * Common Trace Format metadata visitor (generates CTF IR objects).
 */

#define BT_COMP_LOG_SELF_COMP       (ctx->log_cfg.self_comp)
#define BT_COMP_LOG_SELF_COMP_CLASS (ctx->log_cfg.self_comp_class)
#define BT_LOG_OUTPUT_LEVEL         (ctx->log_cfg.log_level)
#define BT_LOG_TAG                  "PLUGIN/CTF/META/IR-VISITOR"
#include "logging/comp-logging.h"

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <ctype.h>
#include "common/assert.h"
#include <glib.h>
#include <inttypes.h>
#include <errno.h>
#include "common/common.h"
#include "common/uuid.h"
#include "compat/endian.h"
#include <babeltrace2/babeltrace.h>

#include "logging.hpp"
#include "scanner.hpp"
#include "ast.hpp"
#include "decoder.hpp"
#include "ctf-meta.hpp"
#include "ctf-meta-visitors.hpp"

/* Bit value (left shift) */
#define _BV(_val) (1 << (_val))

/* Bit is set in a set of bits */
#define _IS_SET(_set, _mask) (*(_set) & (_mask))

/* Set bit in a set of bits */
#define _SET(_set, _mask) (*(_set) |= (_mask))

/* Try to push scope, or go to the `error` label */
#define _TRY_PUSH_SCOPE_OR_GOTO_ERROR()                                                            \
    do {                                                                                           \
        ret = ctx_push_scope(ctx);                                                                 \
        if (ret) {                                                                                 \
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot push scope.");                        \
            goto error;                                                                            \
        }                                                                                          \
    } while (0)

/* Bits for verifying existing attributes in various declarations */
enum
{
    _CLOCK_NAME_SET = _BV(0),
    _CLOCK_UUID_SET = _BV(1),
    _CLOCK_FREQ_SET = _BV(2),
    _CLOCK_PRECISION_SET = _BV(3),
    _CLOCK_OFFSET_S_SET = _BV(4),
    _CLOCK_OFFSET_SET = _BV(5),
    _CLOCK_ABSOLUTE_SET = _BV(6),
    _CLOCK_DESCRIPTION_SET = _BV(7),
};

enum
{
    _INTEGER_ALIGN_SET = _BV(0),
    _INTEGER_SIZE_SET = _BV(1),
    _INTEGER_BASE_SET = _BV(2),
    _INTEGER_ENCODING_SET = _BV(3),
    _INTEGER_BYTE_ORDER_SET = _BV(4),
    _INTEGER_SIGNED_SET = _BV(5),
    _INTEGER_MAP_SET = _BV(6),
};

enum
{
    _FLOAT_ALIGN_SET = _BV(0),
    _FLOAT_MANT_DIG_SET = _BV(1),
    _FLOAT_EXP_DIG_SET = _BV(2),
    _FLOAT_BYTE_ORDER_SET = _BV(3),
};

enum
{
    _STRING_ENCODING_SET = _BV(0),
};

enum
{
    _TRACE_MINOR_SET = _BV(0),
    _TRACE_MAJOR_SET = _BV(1),
    _TRACE_BYTE_ORDER_SET = _BV(2),
    _TRACE_UUID_SET = _BV(3),
    _TRACE_PACKET_HEADER_SET = _BV(4),
};

enum
{
    _STREAM_ID_SET = _BV(0),
    _STREAM_PACKET_CONTEXT_SET = _BV(1),
    _STREAM_EVENT_HEADER_SET = _BV(2),
    _STREAM_EVENT_CONTEXT_SET = _BV(3),
};

enum
{
    _EVENT_NAME_SET = _BV(0),
    _EVENT_ID_SET = _BV(1),
    _EVENT_MODEL_EMF_URI_SET = _BV(2),
    _EVENT_STREAM_ID_SET = _BV(3),
    _EVENT_LOG_LEVEL_SET = _BV(4),
    _EVENT_CONTEXT_SET = _BV(5),
    _EVENT_FIELDS_SET = _BV(6),
};

enum loglevel
{
    LOG_LEVEL_EMERG = 0,
    LOG_LEVEL_ALERT = 1,
    LOG_LEVEL_CRIT = 2,
    LOG_LEVEL_ERR = 3,
    LOG_LEVEL_WARNING = 4,
    LOG_LEVEL_NOTICE = 5,
    LOG_LEVEL_INFO = 6,
    LOG_LEVEL_DEBUG_SYSTEM = 7,
    LOG_LEVEL_DEBUG_PROGRAM = 8,
    LOG_LEVEL_DEBUG_PROCESS = 9,
    LOG_LEVEL_DEBUG_MODULE = 10,
    LOG_LEVEL_DEBUG_UNIT = 11,
    LOG_LEVEL_DEBUG_FUNCTION = 12,
    LOG_LEVEL_DEBUG_LINE = 13,
    LOG_LEVEL_DEBUG = 14,
    _NR_LOGLEVELS = 15,
};

/* Prefixes of class aliases */
#define _PREFIX_ALIAS   'a'
#define _PREFIX_ENUM    'e'
#define _PREFIX_STRUCT  's'
#define _PREFIX_VARIANT 'v'

/* First entry in a BT list */
#define _BT_LIST_FIRST_ENTRY(_ptr, _class, _member) bt_list_entry((_ptr)->next, _class, _member)

#define _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(_node, _attr, _entity)                                 \
    _BT_COMP_LOGE_APPEND_CAUSE_LINENO(                                                             \
        (_node)->lineno, "Duplicate attribute in %s: attr-name=\"%s\"", _entity, _attr)

#define _BT_COMP_LOGE_NODE(_node, _msg, args...) _BT_COMP_LOGE_LINENO((_node)->lineno, _msg, ##args)

#define _BT_COMP_LOGE_APPEND_CAUSE_NODE(_node, _msg, args...)                                      \
    _BT_COMP_LOGE_APPEND_CAUSE_LINENO((_node)->lineno, _msg, ##args)

#define _BT_COMP_LOGW_NODE(_node, _msg, args...) _BT_COMP_LOGW_LINENO((_node)->lineno, _msg, ##args)

#define _BT_COMP_LOGT_NODE(_node, _msg, args...) _BT_COMP_LOGT_LINENO((_node)->lineno, _msg, ##args)

/*
 * Declaration scope of a visitor context. This represents a TSDL
 * lexical scope, so that aliases and named structures, variants,
 * and enumerations may be registered and looked up hierarchically.
 */
struct ctx_decl_scope
{
    /*
     * Alias name to field class.
     *
     * GQuark -> struct ctf_field_class * (owned by this)
     */
    GHashTable *decl_map;

    /* Parent scope; NULL if this is the root declaration scope */
    struct ctx_decl_scope *parent_scope;
};

/*
 * Visitor context (private).
 */
struct ctf_visitor_generate_ir
{
    struct meta_log_config log_cfg;

    /* Trace IR trace class being filled (owned by this) */
    bt_trace_class *trace_class;

    /* CTF meta trace being filled (owned by this) */
    struct ctf_trace_class *ctf_tc;

    /* Current declaration scope (top of the stack) (owned by this) */
    struct ctx_decl_scope *current_scope;

    /* True if trace declaration is visited */
    bool is_trace_visited;

    /* True if this is an LTTng trace */
    bool is_lttng;

    /* Config passed by the user */
    struct ctf_metadata_decoder_config decoder_config;
};

/*
 * Visitor (public).
 */
struct ctf_visitor_generate_ir;

/**
 * Creates a new declaration scope.
 *
 * @param par_scope	Parent scope (NULL if creating a root scope)
 * @returns		New declaration scope, or NULL on error
 */
static struct ctx_decl_scope *ctx_decl_scope_create(struct ctf_visitor_generate_ir *ctx,
                                                    struct ctx_decl_scope *par_scope)
{
    struct ctx_decl_scope *scope;

    scope = g_new(struct ctx_decl_scope, 1);
    if (!scope) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Failed to allocate one declaration scope.");
        goto end;
    }

    scope->decl_map = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL,
                                            (GDestroyNotify) ctf_field_class_destroy);
    scope->parent_scope = par_scope;

end:
    return scope;
}

/**
 * Destroys a declaration scope.
 *
 * This function does not destroy the parent scope.
 *
 * @param scope	Scope to destroy
 */
static void ctx_decl_scope_destroy(struct ctx_decl_scope *scope)
{
    if (!scope) {
        goto end;
    }

    g_hash_table_destroy(scope->decl_map);
    g_free(scope);

end:
    return;
}

/**
 * Returns the GQuark of a prefixed alias.
 *
 * @param prefix	Prefix character
 * @param name		Name
 * @returns		Associated GQuark, or 0 on error
 */
static GQuark get_prefixed_named_quark(struct ctf_visitor_generate_ir *ctx, char prefix,
                                       const char *name)
{
    GQuark qname = 0;

    BT_ASSERT(name);

    /* Prefix character + original string + '\0' */
    char *prname = g_new(char, strlen(name) + 2);
    if (!prname) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Failed to allocate a string.");
        goto end;
    }

    sprintf(prname, "%c%s", prefix, name);
    qname = g_quark_from_string(prname);
    g_free(prname);

end:
    return qname;
}

/**
 * Looks up a prefixed class alias within a declaration scope.
 *
 * @param scope		Declaration scope
 * @param prefix	Prefix character
 * @param name		Alias name
 * @param levels	Number of levels to dig into (-1 means infinite)
 * @param copy		True to return a copy
 * @returns		Declaration (owned by caller if \p copy is true),
 *			or NULL if not found
 */
static struct ctf_field_class *
ctx_decl_scope_lookup_prefix_alias(struct ctf_visitor_generate_ir *ctx,
                                   struct ctx_decl_scope *scope, char prefix, const char *name,
                                   int levels, bool copy)
{
    GQuark qname = 0;
    int cur_levels = 0;
    struct ctf_field_class *decl = NULL;
    struct ctx_decl_scope *cur_scope = scope;

    BT_ASSERT(scope);
    BT_ASSERT(name);
    qname = get_prefixed_named_quark(ctx, prefix, name);
    if (!qname) {
        goto end;
    }

    if (levels < 0) {
        levels = INT_MAX;
    }

    while (cur_scope && cur_levels < levels) {
        decl = (ctf_field_class *) g_hash_table_lookup(cur_scope->decl_map,
                                                       (gconstpointer) GUINT_TO_POINTER(qname));
        if (decl) {
            /* Caller's reference */
            if (copy) {
                decl = ctf_field_class_copy(decl);
                BT_ASSERT(decl);
            }

            goto end;
        }

        cur_scope = cur_scope->parent_scope;
        cur_levels++;
    }

end:
    return decl;
}

/**
 * Looks up a class alias within a declaration scope.
 *
 * @param scope		Declaration scope
 * @param name		Alias name
 * @param levels	Number of levels to dig into (-1 means infinite)
 * @param copy		True to return a copy
 * @returns		Declaration (owned by caller if \p copy is true),
 *			or NULL if not found
 */
static struct ctf_field_class *ctx_decl_scope_lookup_alias(struct ctf_visitor_generate_ir *ctx,
                                                           struct ctx_decl_scope *scope,
                                                           const char *name, int levels, bool copy)
{
    return ctx_decl_scope_lookup_prefix_alias(ctx, scope, _PREFIX_ALIAS, name, levels, copy);
}

/**
 * Looks up an enumeration within a declaration scope.
 *
 * @param scope		Declaration scope
 * @param name		Enumeration name
 * @param levels	Number of levels to dig into (-1 means infinite)
 * @param copy		True to return a copy
 * @returns		Declaration (owned by caller if \p copy is true),
 *			or NULL if not found
 */
static struct ctf_field_class_enum *ctx_decl_scope_lookup_enum(struct ctf_visitor_generate_ir *ctx,
                                                               struct ctx_decl_scope *scope,
                                                               const char *name, int levels,
                                                               bool copy)
{
    return ctf_field_class_as_enum(
        ctx_decl_scope_lookup_prefix_alias(ctx, scope, _PREFIX_ENUM, name, levels, copy));
}

/**
 * Looks up a structure within a declaration scope.
 *
 * @param scope		Declaration scope
 * @param name		Structure name
 * @param levels	Number of levels to dig into (-1 means infinite)
 * @param copy		True to return a copy
 * @returns		Declaration (owned by caller if \p copy is true),
 *			or NULL if not found
 */
static struct ctf_field_class_struct *
ctx_decl_scope_lookup_struct(struct ctf_visitor_generate_ir *ctx, struct ctx_decl_scope *scope,
                             const char *name, int levels, bool copy)
{
    return ctf_field_class_as_struct(
        ctx_decl_scope_lookup_prefix_alias(ctx, scope, _PREFIX_STRUCT, name, levels, copy));
}

/**
 * Looks up a variant within a declaration scope.
 *
 * @param scope		Declaration scope
 * @param name		Variant name
 * @param levels	Number of levels to dig into (-1 means infinite)
 * @param copy		True to return a copy
 * @returns		Declaration (owned by caller if \p copy is true),
 *			or NULL if not found
 */
static struct ctf_field_class_variant *
ctx_decl_scope_lookup_variant(struct ctf_visitor_generate_ir *ctx, struct ctx_decl_scope *scope,
                              const char *name, int levels, bool copy)
{
    return ctf_field_class_as_variant(
        ctx_decl_scope_lookup_prefix_alias(ctx, scope, _PREFIX_VARIANT, name, levels, copy));
}

/**
 * Registers a prefixed class alias within a declaration scope.
 *
 * @param scope		Declaration scope
 * @param prefix	Prefix character
 * @param name		Alias name (non-NULL)
 * @param decl		Field class to register (copied)
 * @returns		0 if registration went okay, negative value otherwise
 */
static int ctx_decl_scope_register_prefix_alias(struct ctf_visitor_generate_ir *ctx,
                                                struct ctx_decl_scope *scope, char prefix,
                                                const char *name, struct ctf_field_class *decl)
{
    int ret = 0;
    GQuark qname = 0;

    BT_ASSERT(scope);
    BT_ASSERT(name);
    BT_ASSERT(decl);
    qname = get_prefixed_named_quark(ctx, prefix, name);
    if (!qname) {
        ret = -ENOMEM;
        goto end;
    }

    /* Make sure alias does not exist in local scope */
    if (ctx_decl_scope_lookup_prefix_alias(ctx, scope, prefix, name, 1, false)) {
        ret = -EEXIST;
        goto end;
    }

    decl = ctf_field_class_copy(decl);
    BT_ASSERT(decl);
    g_hash_table_insert(scope->decl_map, GUINT_TO_POINTER(qname), decl);

end:
    return ret;
}

/**
 * Registers a class alias within a declaration scope.
 *
 * @param scope	Declaration scope
 * @param name	Alias name (non-NULL)
 * @param decl	Field class to register (copied)
 * @returns	0 if registration went okay, negative value otherwise
 */
static int ctx_decl_scope_register_alias(struct ctf_visitor_generate_ir *ctx,
                                         struct ctx_decl_scope *scope, const char *name,
                                         struct ctf_field_class *decl)
{
    return ctx_decl_scope_register_prefix_alias(ctx, scope, _PREFIX_ALIAS, name, decl);
}

/**
 * Registers an enumeration declaration within a declaration scope.
 *
 * @param scope	Declaration scope
 * @param name	Enumeration name (non-NULL)
 * @param decl	Enumeration field class to register (copied)
 * @returns	0 if registration went okay, negative value otherwise
 */
static int ctx_decl_scope_register_enum(struct ctf_visitor_generate_ir *ctx,
                                        struct ctx_decl_scope *scope, const char *name,
                                        struct ctf_field_class_enum *decl)
{
    return ctx_decl_scope_register_prefix_alias(ctx, scope, _PREFIX_ENUM, name,
                                                &decl->base.base.base);
}

/**
 * Registers a structure declaration within a declaration scope.
 *
 * @param scope	Declaration scope
 * @param name	Structure name (non-NULL)
 * @param decl	Structure field class to register (copied)
 * @returns	0 if registration went okay, negative value otherwise
 */
static int ctx_decl_scope_register_struct(struct ctf_visitor_generate_ir *ctx,
                                          struct ctx_decl_scope *scope, const char *name,
                                          struct ctf_field_class_struct *decl)
{
    return ctx_decl_scope_register_prefix_alias(ctx, scope, _PREFIX_STRUCT, name, &decl->base);
}

/**
 * Registers a variant declaration within a declaration scope.
 *
 * @param scope	Declaration scope
 * @param name	Variant name (non-NULL)
 * @param decl	Variant field class to register
 * @returns	0 if registration went okay, negative value otherwise
 */
static int ctx_decl_scope_register_variant(struct ctf_visitor_generate_ir *ctx,
                                           struct ctx_decl_scope *scope, const char *name,
                                           struct ctf_field_class_variant *decl)
{
    return ctx_decl_scope_register_prefix_alias(ctx, scope, _PREFIX_VARIANT, name, &decl->base);
}

/**
 * Destroys a visitor context.
 *
 * @param ctx	Visitor context to destroy
 */
static void ctx_destroy(struct ctf_visitor_generate_ir *ctx)
{
    struct ctx_decl_scope *scope;

    if (!ctx) {
        goto end;
    }

    scope = ctx->current_scope;

    /*
     * Destroy all scopes, from current one to the root scope.
     */
    while (scope) {
        struct ctx_decl_scope *parent_scope = scope->parent_scope;

        ctx_decl_scope_destroy(scope);
        scope = parent_scope;
    }

    bt_trace_class_put_ref(ctx->trace_class);

    if (ctx->ctf_tc) {
        ctf_trace_class_destroy(ctx->ctf_tc);
    }

    g_free(ctx);

end:
    return;
}

/**
 * Creates a new visitor context.
 *
 * @param trace	Associated trace
 * @returns	New visitor context, or NULL on error
 */
static struct ctf_visitor_generate_ir *
ctx_create(const struct ctf_metadata_decoder_config *decoder_config)
{
    struct ctf_visitor_generate_ir *ctx = NULL;

    BT_ASSERT(decoder_config);

    ctx = g_new0(struct ctf_visitor_generate_ir, 1);
    if (!ctx) {
        BT_COMP_LOG_CUR_LVL(BT_LOG_ERROR, decoder_config->log_level, decoder_config->self_comp,
                            "Failed to allocate one visitor context.");
        goto error;
    }

    ctx->log_cfg.log_level = decoder_config->log_level;
    ctx->log_cfg.self_comp = decoder_config->self_comp;
    ctx->log_cfg.self_comp_class = decoder_config->self_comp_class;

    if (decoder_config->self_comp) {
        ctx->trace_class = bt_trace_class_create(decoder_config->self_comp);
        if (!ctx->trace_class) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot create empty trace class.");
            goto error;
        }
    }

    ctx->ctf_tc = ctf_trace_class_create();
    if (!ctx->ctf_tc) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot create CTF trace class.");
        goto error;
    }

    /* Root declaration scope */
    ctx->current_scope = ctx_decl_scope_create(ctx, NULL);
    if (!ctx->current_scope) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot create declaration scope.");
        goto error;
    }

    ctx->decoder_config = *decoder_config;
    goto end;

error:
    ctx_destroy(ctx);
    ctx = NULL;

end:
    return ctx;
}

/**
 * Pushes a new declaration scope on top of a visitor context's
 * declaration scope stack.
 *
 * @param ctx	Visitor context
 * @returns	0 on success, or a negative value on error
 */
static int ctx_push_scope(struct ctf_visitor_generate_ir *ctx)
{
    int ret = 0;
    struct ctx_decl_scope *new_scope;

    BT_ASSERT(ctx);
    new_scope = ctx_decl_scope_create(ctx, ctx->current_scope);
    if (!new_scope) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot create declaration scope.");
        ret = -ENOMEM;
        goto end;
    }

    ctx->current_scope = new_scope;

end:
    return ret;
}

static void ctx_pop_scope(struct ctf_visitor_generate_ir *ctx)
{
    struct ctx_decl_scope *parent_scope = NULL;

    BT_ASSERT(ctx);

    if (!ctx->current_scope) {
        goto end;
    }

    parent_scope = ctx->current_scope->parent_scope;
    ctx_decl_scope_destroy(ctx->current_scope);
    ctx->current_scope = parent_scope;

end:
    return;
}

static int visit_field_class_specifier_list(struct ctf_visitor_generate_ir *ctx,
                                            struct ctf_node *ts_list,
                                            struct ctf_field_class **decl);

static int is_unary_string(struct bt_list_head *head)
{
    int ret = TRUE;
    struct ctf_node *node;

    bt_list_for_each_entry (node, head, siblings) {
        if (node->type != NODE_UNARY_EXPRESSION) {
            ret = FALSE;
        }

        if (node->u.unary_expression.type != UNARY_STRING) {
            ret = FALSE;
        }
    }

    return ret;
}

static const char *get_map_clock_name_value(struct bt_list_head *head)
{
    int i = 0;
    struct ctf_node *node;
    const char *name = NULL;

    bt_list_for_each_entry (node, head, siblings) {
        char *src_string;
        int uexpr_type = node->u.unary_expression.type;
        int uexpr_link = node->u.unary_expression.link;
        int cond = node->type != NODE_UNARY_EXPRESSION || uexpr_type != UNARY_STRING ||
                   !((uexpr_link != UNARY_LINK_UNKNOWN) ^ (i == 0));
        if (cond) {
            goto error;
        }

        /* Needs to be chained with . */
        switch (node->u.unary_expression.link) {
        case UNARY_DOTLINK:
            break;
        case UNARY_ARROWLINK:
        case UNARY_DOTDOTDOT:
            goto error;
        default:
            break;
        }

        src_string = node->u.unary_expression.u.string;

        switch (i) {
        case 0:
            if (strcmp("clock", src_string)) {
                goto error;
            }
            break;
        case 1:
            name = src_string;
            break;
        case 2:
            if (strcmp("value", src_string)) {
                goto error;
            }
            break;
        default:
            /* Extra identifier, unknown */
            goto error;
        }

        i++;
    }

    return name;

error:
    return NULL;
}

static int is_unary_unsigned(struct bt_list_head *head)
{
    int ret = TRUE;
    struct ctf_node *node;

    bt_list_for_each_entry (node, head, siblings) {
        if (node->type != NODE_UNARY_EXPRESSION) {
            ret = FALSE;
        }

        if (node->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
            ret = FALSE;
        }
    }

    return ret;
}

static int get_unary_unsigned(struct ctf_visitor_generate_ir *ctx, struct bt_list_head *head,
                              uint64_t *value)
{
    int i = 0;
    int ret = 0;
    struct ctf_node *node;

    *value = 0;

    if (bt_list_empty(head)) {
        ret = -1;
        goto end;
    }

    bt_list_for_each_entry (node, head, siblings) {
        int uexpr_type = node->u.unary_expression.type;
        int uexpr_link = node->u.unary_expression.link;
        int cond = node->type != NODE_UNARY_EXPRESSION || uexpr_type != UNARY_UNSIGNED_CONSTANT ||
                   uexpr_link != UNARY_LINK_UNKNOWN || i != 0;
        if (cond) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Invalid constant unsigned integer.");
            ret = -EINVAL;
            goto end;
        }

        *value = node->u.unary_expression.u.unsigned_constant;
        i++;
    }

end:
    return ret;
}

static int is_unary_signed(struct bt_list_head *head)
{
    int ret = TRUE;
    struct ctf_node *node;

    bt_list_for_each_entry (node, head, siblings) {
        if (node->type != NODE_UNARY_EXPRESSION) {
            ret = FALSE;
        }

        if (node->u.unary_expression.type != UNARY_SIGNED_CONSTANT) {
            ret = FALSE;
        }
    }

    return ret;
}

static int get_unary_signed(struct bt_list_head *head, int64_t *value)
{
    int i = 0;
    int ret = 0;
    struct ctf_node *node;

    bt_list_for_each_entry (node, head, siblings) {
        int uexpr_type = node->u.unary_expression.type;
        int uexpr_link = node->u.unary_expression.link;
        int cond = node->type != NODE_UNARY_EXPRESSION ||
                   (uexpr_type != UNARY_UNSIGNED_CONSTANT && uexpr_type != UNARY_SIGNED_CONSTANT) ||
                   uexpr_link != UNARY_LINK_UNKNOWN || i != 0;
        if (cond) {
            ret = -EINVAL;
            goto end;
        }

        switch (uexpr_type) {
        case UNARY_UNSIGNED_CONSTANT:
            *value = (int64_t) node->u.unary_expression.u.unsigned_constant;
            break;
        case UNARY_SIGNED_CONSTANT:
            *value = node->u.unary_expression.u.signed_constant;
            break;
        default:
            ret = -EINVAL;
            goto end;
        }

        i++;
    }

end:
    return ret;
}

static int get_unary_uuid(struct ctf_visitor_generate_ir *ctx, struct bt_list_head *head,
                          bt_uuid_t uuid)
{
    return ctf_ast_get_unary_uuid(head, uuid, ctx->log_cfg.log_level, ctx->log_cfg.self_comp);
}

static int get_boolean(struct ctf_visitor_generate_ir *ctx, struct ctf_node *unary_expr)
{
    int ret = 0;

    if (unary_expr->type != NODE_UNARY_EXPRESSION) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(unary_expr, "Expecting unary expression: node-type=%d",
                                        unary_expr->type);
        ret = -EINVAL;
        goto end;
    }

    switch (unary_expr->u.unary_expression.type) {
    case UNARY_UNSIGNED_CONSTANT:
        ret = (unary_expr->u.unary_expression.u.unsigned_constant != 0);
        break;
    case UNARY_SIGNED_CONSTANT:
        ret = (unary_expr->u.unary_expression.u.signed_constant != 0);
        break;
    case UNARY_STRING:
    {
        const char *str = unary_expr->u.unary_expression.u.string;

        if (strcmp(str, "true") == 0 || strcmp(str, "TRUE") == 0) {
            ret = TRUE;
        } else if (strcmp(str, "false") == 0 || strcmp(str, "FALSE") == 0) {
            ret = FALSE;
        } else {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(unary_expr, "Unexpected boolean value: value=\"%s\"",
                                            str);
            ret = -EINVAL;
            goto end;
        }
        break;
    }
    default:
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(unary_expr,
                                        "Unexpected unary expression type: node-type=%d",
                                        unary_expr->u.unary_expression.type);
        ret = -EINVAL;
        goto end;
    }

end:
    return ret;
}

static enum ctf_byte_order byte_order_from_unary_expr(struct ctf_visitor_generate_ir *ctx,
                                                      struct ctf_node *unary_expr)
{
    const char *str;
    enum ctf_byte_order bo = CTF_BYTE_ORDER_UNKNOWN;

    if (unary_expr->u.unary_expression.type != UNARY_STRING) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(
            unary_expr, "\"byte_order\" attribute: expecting `be`, `le`, `network`, or `native`.");
        goto end;
    }

    str = unary_expr->u.unary_expression.u.string;

    if (strcmp(str, "be") == 0 || strcmp(str, "network") == 0) {
        bo = CTF_BYTE_ORDER_BIG;
    } else if (strcmp(str, "le") == 0) {
        bo = CTF_BYTE_ORDER_LITTLE;
    } else if (strcmp(str, "native") == 0) {
        bo = CTF_BYTE_ORDER_DEFAULT;
    } else {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(
            unary_expr,
            "Unexpected \"byte_order\" attribute value: "
            "expecting `be`, `le`, `network`, or `native`: value=\"%s\"",
            str);
        goto end;
    }

end:
    return bo;
}

static enum ctf_byte_order get_real_byte_order(struct ctf_visitor_generate_ir *ctx,
                                               struct ctf_node *uexpr)
{
    enum ctf_byte_order bo = byte_order_from_unary_expr(ctx, uexpr);

    if (bo == CTF_BYTE_ORDER_DEFAULT) {
        bo = ctx->ctf_tc->default_byte_order;
    }

    return bo;
}

static int is_align_valid(uint64_t align)
{
    return (align != 0) && !(align & (align - UINT64_C(1)));
}

static int get_class_specifier_name(struct ctf_visitor_generate_ir *ctx,
                                    struct ctf_node *cls_specifier, GString *str)
{
    int ret = 0;

    if (cls_specifier->type != NODE_TYPE_SPECIFIER) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier, "Unexpected node type: node-type=%d",
                                        cls_specifier->type);
        ret = -EINVAL;
        goto end;
    }

    switch (cls_specifier->u.field_class_specifier.type) {
    case TYPESPEC_VOID:
        g_string_append(str, "void");
        break;
    case TYPESPEC_CHAR:
        g_string_append(str, "char");
        break;
    case TYPESPEC_SHORT:
        g_string_append(str, "short");
        break;
    case TYPESPEC_INT:
        g_string_append(str, "int");
        break;
    case TYPESPEC_LONG:
        g_string_append(str, "long");
        break;
    case TYPESPEC_FLOAT:
        g_string_append(str, "float");
        break;
    case TYPESPEC_DOUBLE:
        g_string_append(str, "double");
        break;
    case TYPESPEC_SIGNED:
        g_string_append(str, "signed");
        break;
    case TYPESPEC_UNSIGNED:
        g_string_append(str, "unsigned");
        break;
    case TYPESPEC_BOOL:
        g_string_append(str, "bool");
        break;
    case TYPESPEC_COMPLEX:
        g_string_append(str, "_Complex");
        break;
    case TYPESPEC_IMAGINARY:
        g_string_append(str, "_Imaginary");
        break;
    case TYPESPEC_CONST:
        g_string_append(str, "const");
        break;
    case TYPESPEC_ID_TYPE:
        if (cls_specifier->u.field_class_specifier.id_type) {
            g_string_append(str, cls_specifier->u.field_class_specifier.id_type);
        }
        break;
    case TYPESPEC_STRUCT:
    {
        struct ctf_node *node = cls_specifier->u.field_class_specifier.node;

        if (!node->u._struct.name) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Unexpected empty structure field class name.");
            ret = -EINVAL;
            goto end;
        }

        g_string_append(str, "struct ");
        g_string_append(str, node->u._struct.name);
        break;
    }
    case TYPESPEC_VARIANT:
    {
        struct ctf_node *node = cls_specifier->u.field_class_specifier.node;

        if (!node->u.variant.name) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Unexpected empty variant field class name.");
            ret = -EINVAL;
            goto end;
        }

        g_string_append(str, "variant ");
        g_string_append(str, node->u.variant.name);
        break;
    }
    case TYPESPEC_ENUM:
    {
        struct ctf_node *node = cls_specifier->u.field_class_specifier.node;

        if (!node->u._enum.enum_id) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                node, "Unexpected empty enumeration field class (`enum`) name.");
            ret = -EINVAL;
            goto end;
        }

        g_string_append(str, "enum ");
        g_string_append(str, node->u._enum.enum_id);
        break;
    }
    case TYPESPEC_FLOATING_POINT:
    case TYPESPEC_INTEGER:
    case TYPESPEC_STRING:
    default:
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier->u.field_class_specifier.node,
                                        "Unexpected field class specifier type: %d",
                                        cls_specifier->u.field_class_specifier.type);
        ret = -EINVAL;
        goto end;
    }

end:
    return ret;
}

static int get_class_specifier_list_name(struct ctf_visitor_generate_ir *ctx,
                                         struct ctf_node *cls_specifier_list, GString *str)
{
    int ret = 0;
    struct ctf_node *iter;
    int alias_item_nr = 0;
    struct bt_list_head *head = &cls_specifier_list->u.field_class_specifier_list.head;

    bt_list_for_each_entry (iter, head, siblings) {
        if (alias_item_nr != 0) {
            g_string_append(str, " ");
        }

        alias_item_nr++;
        ret = get_class_specifier_name(ctx, iter, str);
        if (ret) {
            goto end;
        }
    }

end:
    return ret;
}

static GQuark create_class_alias_identifier(struct ctf_visitor_generate_ir *ctx,
                                            struct ctf_node *cls_specifier_list,
                                            struct ctf_node *node_field_class_declarator)
{
    int ret;
    char *str_c;
    GString *str;
    GQuark qalias = 0;
    struct ctf_node *iter;
    struct bt_list_head *pointers = &node_field_class_declarator->u.field_class_declarator.pointers;

    str = g_string_new("");
    ret = get_class_specifier_list_name(ctx, cls_specifier_list, str);
    if (ret) {
        g_string_free(str, TRUE);
        goto end;
    }

    bt_list_for_each_entry (iter, pointers, siblings) {
        g_string_append(str, " *");

        if (iter->u.pointer.const_qualifier) {
            g_string_append(str, " const");
        }
    }

    str_c = g_string_free(str, FALSE);
    qalias = g_quark_from_string(str_c);
    g_free(str_c);

end:
    return qalias;
}

static int visit_field_class_declarator(struct ctf_visitor_generate_ir *ctx,
                                        struct ctf_node *cls_specifier_list, GQuark *field_name,
                                        struct ctf_node *node_field_class_declarator,
                                        struct ctf_field_class **field_decl,
                                        struct ctf_field_class *nested_decl)
{
    /*
     * During this whole function, nested_decl is always OURS,
     * whereas field_decl is an output which we create, but
     * belongs to the caller (it is moved).
     */
    int ret = 0;
    *field_decl = NULL;

    /* Validate field class declarator node */
    if (node_field_class_declarator) {
        if (node_field_class_declarator->u.field_class_declarator.type == TYPEDEC_UNKNOWN) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                node_field_class_declarator, "Unexpected field class declarator type: type=%d",
                node_field_class_declarator->u.field_class_declarator.type);
            ret = -EINVAL;
            goto error;
        }

        /* TODO: GCC bitfields not supported yet */
        if (node_field_class_declarator->u.field_class_declarator.bitfield_len != NULL) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
                                            "GCC bitfields are not supported as of this version.");
            ret = -EPERM;
            goto error;
        }
    }

    /* Find the right nested declaration if not provided */
    if (!nested_decl) {
        struct bt_list_head *pointers =
            &node_field_class_declarator->u.field_class_declarator.pointers;

        if (node_field_class_declarator && !bt_list_empty(pointers)) {
            GQuark qalias;

            /*
             * If we have a pointer declarator, it HAS to
		 	 * be present in the field class aliases (else
		 	 * fail).
             */
            qalias =
                create_class_alias_identifier(ctx, cls_specifier_list, node_field_class_declarator);
            nested_decl = ctx_decl_scope_lookup_alias(ctx, ctx->current_scope,
                                                      g_quark_to_string(qalias), -1, true);
            if (!nested_decl) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
                                                "Cannot find class alias: name=\"%s\"",
                                                g_quark_to_string(qalias));
                ret = -EINVAL;
                goto error;
            }

            if (nested_decl->type == CTF_FIELD_CLASS_TYPE_INT) {
                /* Pointer: force integer's base to 16 */
                struct ctf_field_class_int *int_fc = ctf_field_class_as_int(nested_decl);

                int_fc->disp_base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL;
            }
        } else {
            ret = visit_field_class_specifier_list(ctx, cls_specifier_list, &nested_decl);
            if (ret) {
                BT_ASSERT(!nested_decl);
                goto error;
            }
        }
    }

    BT_ASSERT(nested_decl);

    if (!node_field_class_declarator) {
        *field_decl = nested_decl;
        nested_decl = NULL;
        goto end;
    }

    if (node_field_class_declarator->u.field_class_declarator.type == TYPEDEC_ID) {
        if (node_field_class_declarator->u.field_class_declarator.u.id) {
            const char *id = node_field_class_declarator->u.field_class_declarator.u.id;

            *field_name = g_quark_from_string(id);
        } else {
            *field_name = 0;
        }

        *field_decl = nested_decl;
        nested_decl = NULL;
        goto end;
    } else {
        struct ctf_node *first;
        struct ctf_field_class *decl = NULL;
        struct ctf_field_class *outer_field_decl = NULL;
        struct bt_list_head *length =
            &node_field_class_declarator->u.field_class_declarator.u.nested.length;

        /* Create array/sequence, pass nested_decl as child */
        if (bt_list_empty(length)) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
                                            "Expecting length field reference or value.");
            ret = -EINVAL;
            goto error;
        }

        first = _BT_LIST_FIRST_ENTRY(length, struct ctf_node, siblings);
        if (first->type != NODE_UNARY_EXPRESSION) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(first, "Unexpected node type: node-type=%d",
                                            first->type);
            ret = -EINVAL;
            goto error;
        }

        switch (first->u.unary_expression.type) {
        case UNARY_UNSIGNED_CONSTANT:
        {
            struct ctf_field_class_array *array_decl = NULL;

            array_decl = ctf_field_class_array_create();
            BT_ASSERT(array_decl);
            array_decl->length = first->u.unary_expression.u.unsigned_constant;
            array_decl->base.elem_fc = nested_decl;
            nested_decl = NULL;
            decl = &array_decl->base.base;
            break;
        }
        case UNARY_STRING:
        {
            /* Lookup unsigned integer definition, create seq. */
            struct ctf_field_class_sequence *seq_decl = NULL;
            char *length_name = ctf_ast_concatenate_unary_strings(length);

            if (!length_name) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
                                                "Cannot concatenate unary strings.");
                ret = -EINVAL;
                goto error;
            }

            if (strncmp(length_name, "env.", 4) == 0) {
                /* This is, in fact, an array */
                const char *env_entry_name = &length_name[4];
                struct ctf_trace_class_env_entry *env_entry =
                    ctf_trace_class_borrow_env_entry_by_name(ctx->ctf_tc, env_entry_name);
                struct ctf_field_class_array *array_decl;

                if (!env_entry) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
                                                    "Cannot find environment entry: "
                                                    "name=\"%s\"",
                                                    env_entry_name);
                    ret = -EINVAL;
                    goto error;
                }

                if (env_entry->type != CTF_TRACE_CLASS_ENV_ENTRY_TYPE_INT) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
                                                    "Wrong environment entry type "
                                                    "(expecting integer): "
                                                    "name=\"%s\"",
                                                    env_entry_name);
                    ret = -EINVAL;
                    goto error;
                }

                if (env_entry->value.i < 0) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node_field_class_declarator,
                                                    "Invalid, negative array length: "
                                                    "env-entry-name=\"%s\", "
                                                    "value=%" PRId64,
                                                    env_entry_name, env_entry->value.i);
                    ret = -EINVAL;
                    goto error;
                }

                array_decl = ctf_field_class_array_create();
                BT_ASSERT(array_decl);
                array_decl->length = (uint64_t) env_entry->value.i;
                array_decl->base.elem_fc = nested_decl;
                nested_decl = NULL;
                decl = &array_decl->base.base;
            } else {
                seq_decl = ctf_field_class_sequence_create();
                BT_ASSERT(seq_decl);
                seq_decl->base.elem_fc = nested_decl;
                nested_decl = NULL;
                g_string_assign(seq_decl->length_ref, length_name);
                decl = &seq_decl->base.base;
            }

            g_free(length_name);
            break;
        }
        default:
            ret = -EINVAL;
            goto error;
        }

        BT_ASSERT(!nested_decl);
        BT_ASSERT(decl);
        BT_ASSERT(!*field_decl);

        /*
         * At this point, we found the next nested declaration.
         * We currently own this (and lost the ownership of
         * nested_decl in the meantime). Pass this next
         * nested declaration as the content of the outer
         * container, MOVING its ownership.
         */
        ret = visit_field_class_declarator(
            ctx, cls_specifier_list, field_name,
            node_field_class_declarator->u.field_class_declarator.u.nested.field_class_declarator,
            &outer_field_decl, decl);
        decl = NULL;
        if (ret) {
            BT_ASSERT(!outer_field_decl);
            ret = -EINVAL;
            goto error;
        }

        BT_ASSERT(outer_field_decl);
        *field_decl = outer_field_decl;
        outer_field_decl = NULL;
    }

    BT_ASSERT(*field_decl);
    goto end;

error:
    ctf_field_class_destroy(*field_decl);
    *field_decl = NULL;

    if (ret >= 0) {
        ret = -1;
    }

end:
    ctf_field_class_destroy(nested_decl);
    nested_decl = NULL;
    return ret;
}

static int visit_struct_decl_field(struct ctf_visitor_generate_ir *ctx,
                                   struct ctf_field_class_struct *struct_decl,
                                   struct ctf_node *cls_specifier_list,
                                   struct bt_list_head *field_class_declarators)
{
    int ret = 0;
    struct ctf_node *iter;
    struct ctf_field_class *field_decl = NULL;

    bt_list_for_each_entry (iter, field_class_declarators, siblings) {
        field_decl = NULL;
        GQuark qfield_name;
        const char *field_name;

        ret = visit_field_class_declarator(ctx, cls_specifier_list, &qfield_name, iter, &field_decl,
                                           NULL);
        if (ret) {
            BT_ASSERT(!field_decl);
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
                                            "Cannot visit field class declarator: ret=%d", ret);
            goto error;
        }

        BT_ASSERT(field_decl);
        field_name = g_quark_to_string(qfield_name);

        /* Check if field with same name already exists */
        if (ctf_field_class_struct_borrow_member_by_name(struct_decl, field_name)) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
                                            "Duplicate field in structure field class: "
                                            "field-name=\"%s\"",
                                            field_name);
            ret = -EINVAL;
            goto error;
        }

        /* Add field to structure */
        ctf_field_class_struct_append_member(struct_decl, field_name, field_decl);
        field_decl = NULL;
    }

    return 0;

error:
    ctf_field_class_destroy(field_decl);
    field_decl = NULL;
    return ret;
}

static int visit_variant_decl_field(struct ctf_visitor_generate_ir *ctx,
                                    struct ctf_field_class_variant *variant_decl,
                                    struct ctf_node *cls_specifier_list,
                                    struct bt_list_head *field_class_declarators)
{
    int ret = 0;
    struct ctf_node *iter;
    struct ctf_field_class *field_decl = NULL;

    bt_list_for_each_entry (iter, field_class_declarators, siblings) {
        field_decl = NULL;
        GQuark qfield_name;
        const char *field_name;

        ret = visit_field_class_declarator(ctx, cls_specifier_list, &qfield_name, iter, &field_decl,
                                           NULL);
        if (ret) {
            BT_ASSERT(!field_decl);
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
                                            "Cannot visit field class declarator: ret=%d", ret);
            goto error;
        }

        BT_ASSERT(field_decl);
        field_name = g_quark_to_string(qfield_name);

        /* Check if field with same name already exists */
        if (ctf_field_class_variant_borrow_option_by_name(variant_decl, field_name)) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
                                            "Duplicate field in variant field class: "
                                            "field-name=\"%s\"",
                                            field_name);
            ret = -EINVAL;
            goto error;
        }

        /* Add field to structure */
        ctf_field_class_variant_append_option(variant_decl, field_name, field_decl);
        field_decl = NULL;
    }

    return 0;

error:
    ctf_field_class_destroy(field_decl);
    field_decl = NULL;
    return ret;
}

static int visit_field_class_def(struct ctf_visitor_generate_ir *ctx,
                                 struct ctf_node *cls_specifier_list,
                                 struct bt_list_head *field_class_declarators)
{
    int ret = 0;
    GQuark qidentifier;
    struct ctf_node *iter;
    struct ctf_field_class *class_decl = NULL;

    bt_list_for_each_entry (iter, field_class_declarators, siblings) {
        ret = visit_field_class_declarator(ctx, cls_specifier_list, &qidentifier, iter, &class_decl,
                                           NULL);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit field class declarator: ret=%d",
                                            ret);
            ret = -EINVAL;
            goto end;
        }

        /* Do not allow field class def and alias of untagged variants */
        if (class_decl->type == CTF_FIELD_CLASS_TYPE_VARIANT) {
            struct ctf_field_class_variant *var_fc = ctf_field_class_as_variant(class_decl);

            if (var_fc->tag_path.path->len == 0) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    iter, "Type definition of untagged variant field class is not allowed.");
                ret = -EPERM;
                goto end;
            }
        }

        ret = ctx_decl_scope_register_alias(ctx, ctx->current_scope, g_quark_to_string(qidentifier),
                                            class_decl);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot register field class alias: name=\"%s\"",
                                            g_quark_to_string(qidentifier));
            goto end;
        }
    }

end:
    ctf_field_class_destroy(class_decl);
    class_decl = NULL;
    return ret;
}

static int visit_field_class_alias(struct ctf_visitor_generate_ir *ctx, struct ctf_node *target,
                                   struct ctf_node *alias)
{
    int ret = 0;
    GQuark qalias;
    struct ctf_node *node;
    GQuark qdummy_field_name;
    struct ctf_field_class *class_decl = NULL;

    /* Create target field class */
    if (bt_list_empty(&target->u.field_class_alias_target.field_class_declarators)) {
        node = NULL;
    } else {
        node = _BT_LIST_FIRST_ENTRY(&target->u.field_class_alias_target.field_class_declarators,
                                    struct ctf_node, siblings);
    }

    ret = visit_field_class_declarator(
        ctx, target->u.field_class_alias_target.field_class_specifier_list, &qdummy_field_name,
        node, &class_decl, NULL);
    if (ret) {
        BT_ASSERT(!class_decl);
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot visit field class declarator: ret=%d", ret);
        goto end;
    }

    /* Do not allow field class def and alias of untagged variants */
    if (class_decl->type == CTF_FIELD_CLASS_TYPE_VARIANT) {
        struct ctf_field_class_variant *var_fc = ctf_field_class_as_variant(class_decl);

        if (var_fc->tag_path.path->len == 0) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                target, "Type definition of untagged variant field class is not allowed.");
            ret = -EPERM;
            goto end;
        }
    }

    /*
     * The semantic validator does not check whether the target is
     * abstract or not (if it has an identifier). Check it here.
     */
    if (qdummy_field_name != 0) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(target, "Expecting empty identifier: id=\"%s\"",
                                        g_quark_to_string(qdummy_field_name));
        ret = -EINVAL;
        goto end;
    }

    /* Create alias identifier */
    node = _BT_LIST_FIRST_ENTRY(&alias->u.field_class_alias_name.field_class_declarators,
                                struct ctf_node, siblings);
    qalias = create_class_alias_identifier(
        ctx, alias->u.field_class_alias_name.field_class_specifier_list, node);
    ret = ctx_decl_scope_register_alias(ctx, ctx->current_scope, g_quark_to_string(qalias),
                                        class_decl);
    if (ret) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot register class alias: name=\"%s\"",
                                        g_quark_to_string(qalias));
        goto end;
    }

end:
    ctf_field_class_destroy(class_decl);
    class_decl = NULL;
    return ret;
}

static int visit_struct_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *entry_node,
                                   struct ctf_field_class_struct *struct_decl)
{
    int ret = 0;

    switch (entry_node->type) {
    case NODE_TYPEDEF:
        ret = visit_field_class_def(ctx, entry_node->u.field_class_def.field_class_specifier_list,
                                    &entry_node->u.field_class_def.field_class_declarators);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Cannot add field class found in structure field class: ret=%d", ret);
            goto end;
        }
        break;
    case NODE_TYPEALIAS:
        ret = visit_field_class_alias(ctx, entry_node->u.field_class_alias.target,
                                      entry_node->u.field_class_alias.alias);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Cannot add field class alias found in structure field class: ret=%d",
                ret);
            goto end;
        }
        break;
    case NODE_STRUCT_OR_VARIANT_DECLARATION:
        /* Field */
        ret = visit_struct_decl_field(
            ctx, struct_decl,
            entry_node->u.struct_or_variant_declaration.field_class_specifier_list,
            &entry_node->u.struct_or_variant_declaration.field_class_declarators);
        if (ret) {
            goto end;
        }
        break;
    default:
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Unexpected node type: node-type=%d",
                                        entry_node->type);
        ret = -EINVAL;
        goto end;
    }

end:
    return ret;
}

static int visit_variant_decl_entry(struct ctf_visitor_generate_ir *ctx,
                                    struct ctf_node *entry_node,
                                    struct ctf_field_class_variant *variant_decl)
{
    int ret = 0;

    switch (entry_node->type) {
    case NODE_TYPEDEF:
        ret = visit_field_class_def(ctx, entry_node->u.field_class_def.field_class_specifier_list,
                                    &entry_node->u.field_class_def.field_class_declarators);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Cannot add field class found in variant field class: ret=%d", ret);
            goto end;
        }
        break;
    case NODE_TYPEALIAS:
        ret = visit_field_class_alias(ctx, entry_node->u.field_class_alias.target,
                                      entry_node->u.field_class_alias.alias);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Cannot add field class alias found in variant field class: ret=%d",
                ret);
            goto end;
        }
        break;
    case NODE_STRUCT_OR_VARIANT_DECLARATION:
        /* Field */
        ret = visit_variant_decl_field(
            ctx, variant_decl,
            entry_node->u.struct_or_variant_declaration.field_class_specifier_list,
            &entry_node->u.struct_or_variant_declaration.field_class_declarators);
        if (ret) {
            goto end;
        }
        break;
    default:
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Unexpected node type: node-type=%d",
                                        entry_node->type);
        ret = -EINVAL;
        goto end;
    }

end:
    return ret;
}

static int visit_struct_decl(struct ctf_visitor_generate_ir *ctx, const char *name,
                             struct bt_list_head *decl_list, int has_body,
                             struct bt_list_head *min_align,
                             struct ctf_field_class_struct **struct_decl)
{
    int ret = 0;

    BT_ASSERT(struct_decl);
    *struct_decl = NULL;

    /* For named struct (without body), lookup in declaration scope */
    if (!has_body) {
        if (!name) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                "Bodyless structure field class: missing name.");
            ret = -EPERM;
            goto error;
        }

        *struct_decl = ctx_decl_scope_lookup_struct(ctx, ctx->current_scope, name, -1, true);
        if (!*struct_decl) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                "Cannot find structure field class: name=\"struct %s\"", name);
            ret = -EINVAL;
            goto error;
        }
    } else {
        struct ctf_node *entry_node;
        uint64_t min_align_value = 0;

        if (name) {
            if (ctx_decl_scope_lookup_struct(ctx, ctx->current_scope, name, 1, false)) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Structure field class already declared in local scope: "
                    "name=\"struct %s\"",
                    name);
                ret = -EINVAL;
                goto error;
            }
        }

        if (!bt_list_empty(min_align)) {
            ret = get_unary_unsigned(ctx, min_align, &min_align_value);
            if (ret) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Unexpected unary expression for structure field class's `align` attribute: "
                    "ret=%d",
                    ret);
                goto error;
            }
        }

        *struct_decl = ctf_field_class_struct_create();
        BT_ASSERT(*struct_decl);

        if (min_align_value != 0) {
            (*struct_decl)->base.alignment = min_align_value;
        }

        _TRY_PUSH_SCOPE_OR_GOTO_ERROR();

        bt_list_for_each_entry (entry_node, decl_list, siblings) {
            ret = visit_struct_decl_entry(ctx, entry_node, *struct_decl);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node,
                                                "Cannot visit structure field class entry: "
                                                "ret=%d",
                                                ret);
                ctx_pop_scope(ctx);
                goto error;
            }
        }

        ctx_pop_scope(ctx);

        if (name) {
            ret = ctx_decl_scope_register_struct(ctx, ctx->current_scope, name, *struct_decl);
            if (ret) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Cannot register structure field class in declaration scope: "
                    "name=\"struct %s\", ret=%d",
                    name, ret);
                goto error;
            }
        }
    }

    return 0;

error:
    ctf_field_class_destroy(&(*struct_decl)->base);
    *struct_decl = NULL;
    return ret;
}

static int visit_variant_decl(struct ctf_visitor_generate_ir *ctx, const char *name,
                              const char *tag, struct bt_list_head *decl_list, int has_body,
                              struct ctf_field_class_variant **variant_decl)
{
    int ret = 0;
    struct ctf_field_class_variant *untagged_variant_decl = NULL;

    BT_ASSERT(variant_decl);
    *variant_decl = NULL;

    /* For named variant (without body), lookup in declaration scope */
    if (!has_body) {
        if (!name) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Bodyless variant field class: missing name.");
            ret = -EPERM;
            goto error;
        }

        untagged_variant_decl =
            ctx_decl_scope_lookup_variant(ctx, ctx->current_scope, name, -1, true);
        if (!untagged_variant_decl) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                "Cannot find variant field class: name=\"variant %s\"", name);
            ret = -EINVAL;
            goto error;
        }
    } else {
        struct ctf_node *entry_node;

        if (name) {
            if (ctx_decl_scope_lookup_variant(ctx, ctx->current_scope, name, 1, false)) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Variant field class already declared in local scope: "
                    "name=\"variant %s\"",
                    name);
                ret = -EINVAL;
                goto error;
            }
        }

        untagged_variant_decl = ctf_field_class_variant_create();
        BT_ASSERT(untagged_variant_decl);
        _TRY_PUSH_SCOPE_OR_GOTO_ERROR();

        bt_list_for_each_entry (entry_node, decl_list, siblings) {
            ret = visit_variant_decl_entry(ctx, entry_node, untagged_variant_decl);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node,
                                                "Cannot visit variant field class entry: "
                                                "ret=%d",
                                                ret);
                ctx_pop_scope(ctx);
                goto error;
            }
        }

        ctx_pop_scope(ctx);

        if (name) {
            ret = ctx_decl_scope_register_variant(ctx, ctx->current_scope, name,
                                                  untagged_variant_decl);
            if (ret) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Cannot register variant field class in declaration scope: "
                    "name=\"variant %s\", ret=%d",
                    name, ret);
                goto error;
            }
        }
    }

    /*
     * If tagged, create tagged variant and return; otherwise
     * return untagged variant.
     */
    if (!tag) {
        *variant_decl = untagged_variant_decl;
        untagged_variant_decl = NULL;
    } else {
        /*
         * At this point, we have a fresh untagged variant; nobody
         * else owns it. Set its tag now.
         */
        g_string_assign(untagged_variant_decl->tag_ref, tag);
        *variant_decl = untagged_variant_decl;
        untagged_variant_decl = NULL;
    }

    BT_ASSERT(!untagged_variant_decl);
    BT_ASSERT(*variant_decl);
    return 0;

error:
    ctf_field_class_destroy(&untagged_variant_decl->base);
    untagged_variant_decl = NULL;
    ctf_field_class_destroy(&(*variant_decl)->base);
    *variant_decl = NULL;
    return ret;
}

struct uori
{
    bool is_signed;
    union
    {
        uint64_t u;
        uint64_t i;
    } value;
};

static int visit_enum_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *enumerator,
                                 struct ctf_field_class_enum *enum_decl, struct uori *last)
{
    int ret = 0;
    int nr_vals = 0;
    struct ctf_node *iter;
    struct uori start = {
        .is_signed = false,
        .value =
            {
                .u = 0,
            },
    };
    struct uori end = {
        .is_signed = false,
        .value =
            {
                .u = 0,
            },
    };
    const char *label = enumerator->u.enumerator.id;
    struct bt_list_head *values = &enumerator->u.enumerator.values;

    bt_list_for_each_entry (iter, values, siblings) {
        struct uori *target;

        if (iter->type != NODE_UNARY_EXPRESSION) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
                                            "Wrong expression for enumeration field class label: "
                                            "node-type=%d, label=\"%s\"",
                                            iter->type, label);
            ret = -EINVAL;
            goto error;
        }

        if (nr_vals == 0) {
            target = &start;
        } else {
            target = &end;
        }

        switch (iter->u.unary_expression.type) {
        case UNARY_SIGNED_CONSTANT:
            target->is_signed = true;
            target->value.i = iter->u.unary_expression.u.signed_constant;
            break;
        case UNARY_UNSIGNED_CONSTANT:
            target->is_signed = false;
            target->value.u = iter->u.unary_expression.u.unsigned_constant;
            break;
        default:
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
                                            "Invalid enumeration field class entry: "
                                            "expecting constant signed or unsigned integer: "
                                            "node-type=%d, label=\"%s\"",
                                            iter->u.unary_expression.type, label);
            ret = -EINVAL;
            goto error;
        }

        if (nr_vals > 1) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                iter, "Invalid enumeration field class entry: label=\"%s\"", label);
            ret = -EINVAL;
            goto error;
        }

        nr_vals++;
    }

    if (nr_vals == 0) {
        start = *last;
    }

    if (nr_vals <= 1) {
        end = start;
    }

    if (end.is_signed) {
        last->value.i = end.value.i + 1;
    } else {
        last->value.u = end.value.u + 1;
    }

    ctf_field_class_enum_map_range(enum_decl, label, start.value.u, end.value.u);
    return 0;

error:
    return ret;
}

static int visit_enum_decl(struct ctf_visitor_generate_ir *ctx, const char *name,
                           struct ctf_node *container_cls, struct bt_list_head *enumerator_list,
                           int has_body, struct ctf_field_class_enum **enum_decl)
{
    int ret = 0;
    GQuark qdummy_id;
    struct ctf_field_class_int *integer_decl = NULL;

    BT_ASSERT(enum_decl);
    *enum_decl = NULL;

    /* For named enum (without body), lookup in declaration scope */
    if (!has_body) {
        if (!name) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                "Bodyless enumeration field class: missing name.");
            ret = -EPERM;
            goto error;
        }

        *enum_decl = ctx_decl_scope_lookup_enum(ctx, ctx->current_scope, name, -1, true);
        if (!*enum_decl) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("Cannot find enumeration field class: "
                                                     "name=\"enum %s\"",
                                                     name);
            ret = -EINVAL;
            goto error;
        }
    } else {
        struct ctf_node *iter;
        struct uori last_value = {
            .is_signed = false,
            .value =
                {
                    .u = 0,
                },
        };

        if (name) {
            if (ctx_decl_scope_lookup_enum(ctx, ctx->current_scope, name, 1, false)) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Enumeration field class already declared in local scope: "
                    "name=\"enum %s\"",
                    name);
                ret = -EINVAL;
                goto error;
            }
        }

        if (!container_cls) {
            integer_decl = ctf_field_class_as_int(
                ctx_decl_scope_lookup_alias(ctx, ctx->current_scope, "int", -1, true));
            if (!integer_decl) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Cannot find implicit `int` field class alias for enumeration field class.");
                ret = -EINVAL;
                goto error;
            }
        } else {
            ctf_field_class *decl;

            ret = visit_field_class_declarator(ctx, container_cls, &qdummy_id, NULL, &decl, NULL);
            if (ret) {
                BT_ASSERT(!decl);
                ret = -EINVAL;
                goto error;
            }

            integer_decl = ctf_field_class_as_int(decl);
        }

        BT_ASSERT(integer_decl);

        if (integer_decl->base.base.type != CTF_FIELD_CLASS_TYPE_INT) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                "Container field class for enumeration field class is not an integer field class: "
                "fc-type=%d",
                integer_decl->base.base.type);
            ret = -EINVAL;
            goto error;
        }

        *enum_decl = ctf_field_class_enum_create();
        BT_ASSERT(*enum_decl);
        (*enum_decl)->base.base.base.alignment = integer_decl->base.base.alignment;
        ctf_field_class_int_copy_content(&(*enum_decl)->base, integer_decl);
        last_value.is_signed = (*enum_decl)->base.is_signed;

        bt_list_for_each_entry (iter, enumerator_list, siblings) {
            ret = visit_enum_decl_entry(ctx, iter, *enum_decl, &last_value);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
                                                "Cannot visit enumeration field class entry: "
                                                "ret=%d",
                                                ret);
                goto error;
            }
        }

        if (name) {
            ret = ctx_decl_scope_register_enum(ctx, ctx->current_scope, name, *enum_decl);
            if (ret) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Cannot register enumeration field class in declaration scope: "
                    "ret=%d",
                    ret);
                goto error;
            }
        }
    }

    goto end;

error:
    ctf_field_class_destroy(&(*enum_decl)->base.base.base);
    *enum_decl = NULL;

end:
    ctf_field_class_destroy(&integer_decl->base.base);
    integer_decl = NULL;
    return ret;
}

static int visit_field_class_specifier(struct ctf_visitor_generate_ir *ctx,
                                       struct ctf_node *cls_specifier_list,
                                       struct ctf_field_class **decl)
{
    int ret = 0;
    GString *str = NULL;

    *decl = NULL;
    str = g_string_new("");
    ret = get_class_specifier_list_name(ctx, cls_specifier_list, str);
    if (ret) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(
            cls_specifier_list, "Cannot get field class specifier list's name: ret=%d", ret);
        goto error;
    }

    *decl = ctx_decl_scope_lookup_alias(ctx, ctx->current_scope, str->str, -1, true);
    if (!*decl) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(cls_specifier_list,
                                        "Cannot find field class alias: name=\"%s\"", str->str);
        ret = -EINVAL;
        goto error;
    }

    goto end;

error:
    ctf_field_class_destroy(*decl);
    *decl = NULL;

end:
    if (str) {
        g_string_free(str, TRUE);
    }

    return ret;
}

static int visit_integer_decl(struct ctf_visitor_generate_ir *ctx, struct bt_list_head *expressions,
                              struct ctf_field_class_int **integer_decl)
{
    int set = 0;
    int ret = 0;
    int signedness = 0;
    struct ctf_node *expression;
    uint64_t alignment = 0, size = 0;
    struct ctf_clock_class *mapped_clock_class = NULL;
    enum ctf_encoding encoding = CTF_ENCODING_NONE;
    bt_field_class_integer_preferred_display_base base =
        BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL;
    enum ctf_byte_order byte_order = ctx->ctf_tc->default_byte_order;

    *integer_decl = NULL;

    bt_list_for_each_entry (expression, expressions, siblings) {
        struct ctf_node *left, *right;

        left = _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.left, struct ctf_node, siblings);
        right =
            _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.right, struct ctf_node, siblings);

        if (left->u.unary_expression.type != UNARY_STRING) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(left, "Unexpected unary expression type: type=%d",
                                            left->u.unary_expression.type);
            ret = -EINVAL;
            goto error;
        }

        if (strcmp(left->u.unary_expression.u.string, "signed") == 0) {
            if (_IS_SET(&set, _INTEGER_SIGNED_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "signed", "integer field class");
                ret = -EPERM;
                goto error;
            }

            signedness = get_boolean(ctx, right);
            if (signedness < 0) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid boolean value for integer field class's `signed` attribute: "
                    "ret=%d",
                    ret);
                ret = -EINVAL;
                goto error;
            }

            _SET(&set, _INTEGER_SIGNED_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "byte_order") == 0) {
            if (_IS_SET(&set, _INTEGER_BYTE_ORDER_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "byte_order", "integer field class");
                ret = -EPERM;
                goto error;
            }

            byte_order = get_real_byte_order(ctx, right);
            if (byte_order == CTF_BYTE_ORDER_UNKNOWN) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `byte_order` attribute in integer field class: "
                    "ret=%d",
                    ret);
                ret = -EINVAL;
                goto error;
            }

            _SET(&set, _INTEGER_BYTE_ORDER_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "size") == 0) {
            if (_IS_SET(&set, _INTEGER_SIZE_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "size", "integer field class");
                ret = -EPERM;
                goto error;
            }

            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
                                                "Invalid `size` attribute in integer field class: "
                                                "expecting unsigned constant integer: "
                                                "node-type=%d",
                                                right->u.unary_expression.type);
                ret = -EINVAL;
                goto error;
            }

            size = right->u.unary_expression.u.unsigned_constant;
            if (size == 0) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
                                                "Invalid `size` attribute in integer field class: "
                                                "expecting positive constant integer: "
                                                "size=%" PRIu64,
                                                size);
                ret = -EINVAL;
                goto error;
            } else if (size > 64) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `size` attribute in integer field class: "
                    "integer fields over 64 bits are not supported as of this version: "
                    "size=%" PRIu64,
                    size);
                ret = -EINVAL;
                goto error;
            }

            _SET(&set, _INTEGER_SIZE_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "align") == 0) {
            if (_IS_SET(&set, _INTEGER_ALIGN_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "align", "integer field class");
                ret = -EPERM;
                goto error;
            }

            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
                                                "Invalid `align` attribute in integer field class: "
                                                "expecting unsigned constant integer: "
                                                "node-type=%d",
                                                right->u.unary_expression.type);
                ret = -EINVAL;
                goto error;
            }

            alignment = right->u.unary_expression.u.unsigned_constant;
            if (!is_align_valid(alignment)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
                                                "Invalid `align` attribute in integer field class: "
                                                "expecting power of two: "
                                                "align=%" PRIu64,
                                                alignment);
                ret = -EINVAL;
                goto error;
            }

            _SET(&set, _INTEGER_ALIGN_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "base") == 0) {
            if (_IS_SET(&set, _INTEGER_BASE_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "base", "integer field class");
                ret = -EPERM;
                goto error;
            }

            switch (right->u.unary_expression.type) {
            case UNARY_UNSIGNED_CONSTANT:
            {
                uint64_t constant = right->u.unary_expression.u.unsigned_constant;

                switch (constant) {
                case 2:
                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_BINARY;
                    break;
                case 8:
                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_OCTAL;
                    break;
                case 10:
                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL;
                    break;
                case 16:
                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL;
                    break;
                default:
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                        right,
                        "Invalid `base` attribute in integer field class: "
                        "base=%" PRIu64,
                        right->u.unary_expression.u.unsigned_constant);
                    ret = -EINVAL;
                    goto error;
                }
                break;
            }
            case UNARY_STRING:
            {
                char *s_right =
                    ctf_ast_concatenate_unary_strings(&expression->u.ctf_expression.right);
                if (!s_right) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                        right,
                        "Unexpected unary expression for integer field class's `base` attribute.");
                    ret = -EINVAL;
                    goto error;
                }

                if (strcmp(s_right, "decimal") == 0 || strcmp(s_right, "dec") == 0 ||
                    strcmp(s_right, "d") == 0 || strcmp(s_right, "i") == 0 ||
                    strcmp(s_right, "u") == 0) {
                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_DECIMAL;
                } else if (strcmp(s_right, "hexadecimal") == 0 || strcmp(s_right, "hex") == 0 ||
                           strcmp(s_right, "x") == 0 || strcmp(s_right, "X") == 0 ||
                           strcmp(s_right, "p") == 0) {
                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_HEXADECIMAL;
                } else if (strcmp(s_right, "octal") == 0 || strcmp(s_right, "oct") == 0 ||
                           strcmp(s_right, "o") == 0) {
                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_OCTAL;
                } else if (strcmp(s_right, "binary") == 0 || strcmp(s_right, "b") == 0) {
                    base = BT_FIELD_CLASS_INTEGER_PREFERRED_DISPLAY_BASE_BINARY;
                } else {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                        right,
                        "Unexpected unary expression for integer field class's `base` attribute: "
                        "base=\"%s\"",
                        s_right);
                    g_free(s_right);
                    ret = -EINVAL;
                    goto error;
                }

                g_free(s_right);
                break;
            }
            default:
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right, "Invalid `base` attribute in integer field class: "
                           "expecting unsigned constant integer or unary string.");
                ret = -EINVAL;
                goto error;
            }

            _SET(&set, _INTEGER_BASE_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "encoding") == 0) {
            char *s_right;

            if (_IS_SET(&set, _INTEGER_ENCODING_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "encoding", "integer field class");
                ret = -EPERM;
                goto error;
            }

            if (right->u.unary_expression.type != UNARY_STRING) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right, "Invalid `encoding` attribute in integer field class: "
                           "expecting unary string.");
                ret = -EINVAL;
                goto error;
            }

            s_right = ctf_ast_concatenate_unary_strings(&expression->u.ctf_expression.right);
            if (!s_right) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Unexpected unary expression for integer field class's `encoding` attribute.");
                ret = -EINVAL;
                goto error;
            }

            if (strcmp(s_right, "UTF8") == 0 || strcmp(s_right, "utf8") == 0 ||
                strcmp(s_right, "utf-8") == 0 || strcmp(s_right, "UTF-8") == 0 ||
                strcmp(s_right, "ASCII") == 0 || strcmp(s_right, "ascii") == 0) {
                encoding = CTF_ENCODING_UTF8;
            } else if (strcmp(s_right, "none") == 0) {
                encoding = CTF_ENCODING_NONE;
            } else {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `encoding` attribute in integer field class: "
                    "unknown encoding: encoding=\"%s\"",
                    s_right);
                g_free(s_right);
                ret = -EINVAL;
                goto error;
            }

            g_free(s_right);
            _SET(&set, _INTEGER_ENCODING_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "map") == 0) {
            const char *clock_name;

            if (_IS_SET(&set, _INTEGER_MAP_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "map", "integer field class");
                ret = -EPERM;
                goto error;
            }

            if (right->u.unary_expression.type != UNARY_STRING) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(right,
                                                "Invalid `map` attribute in integer field class: "
                                                "expecting unary string.");
                ret = -EINVAL;
                goto error;
            }

            clock_name = get_map_clock_name_value(&expression->u.ctf_expression.right);
            if (!clock_name) {
                char *s_right =
                    ctf_ast_concatenate_unary_strings(&expression->u.ctf_expression.right);

                if (!s_right) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                        right,
                        "Unexpected unary expression for integer field class's `map` attribute.");
                    ret = -EINVAL;
                    goto error;
                }

                _BT_COMP_LOGE_NODE(right,
                                   "Invalid `map` attribute in integer field class: "
                                   "cannot find clock class at this point: name=\"%s\"",
                                   s_right);
                _SET(&set, _INTEGER_MAP_SET);
                g_free(s_right);
                continue;
            }

            mapped_clock_class =
                ctf_trace_class_borrow_clock_class_by_name(ctx->ctf_tc, clock_name);
            if (!mapped_clock_class) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `map` attribute in integer field class: "
                    "cannot find clock class at this point: name=\"%s\"",
                    clock_name);
                ret = -EINVAL;
                goto error;
            }

            _SET(&set, _INTEGER_MAP_SET);
        } else {
            _BT_COMP_LOGW_NODE(left,
                               "Unknown attribute in integer field class: "
                               "attr-name=\"%s\"",
                               left->u.unary_expression.u.string);
        }
    }

    if (!_IS_SET(&set, _INTEGER_SIZE_SET)) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
            "Missing `size` attribute in integer field class.");
        ret = -EPERM;
        goto error;
    }

    if (!_IS_SET(&set, _INTEGER_ALIGN_SET)) {
        if (size % CHAR_BIT) {
            /* Bit-packed alignment */
            alignment = 1;
        } else {
            /* Byte-packed alignment */
            alignment = CHAR_BIT;
        }
    }

    *integer_decl = ctf_field_class_int_create();
    BT_ASSERT(*integer_decl);
    (*integer_decl)->base.base.alignment = alignment;
    (*integer_decl)->base.byte_order = byte_order;
    (*integer_decl)->base.size = size;
    (*integer_decl)->is_signed = (signedness > 0);
    (*integer_decl)->disp_base = base;
    (*integer_decl)->encoding = encoding;
    (*integer_decl)->mapped_clock_class = mapped_clock_class;
    return 0;

error:
    ctf_field_class_destroy(&(*integer_decl)->base.base);
    *integer_decl = NULL;
    return ret;
}

static int visit_floating_point_number_decl(struct ctf_visitor_generate_ir *ctx,
                                            struct bt_list_head *expressions,
                                            struct ctf_field_class_float **float_decl)
{
    int set = 0;
    int ret = 0;
    struct ctf_node *expression;
    uint64_t alignment = 1, exp_dig = 0, mant_dig = 0;
    enum ctf_byte_order byte_order = ctx->ctf_tc->default_byte_order;

    *float_decl = NULL;

    bt_list_for_each_entry (expression, expressions, siblings) {
        struct ctf_node *left, *right;

        left = _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.left, struct ctf_node, siblings);
        right =
            _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.right, struct ctf_node, siblings);

        if (left->u.unary_expression.type != UNARY_STRING) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(left, "Unexpected unary expression type: type=%d",
                                            left->u.unary_expression.type);
            ret = -EINVAL;
            goto error;
        }

        if (strcmp(left->u.unary_expression.u.string, "byte_order") == 0) {
            if (_IS_SET(&set, _FLOAT_BYTE_ORDER_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "byte_order",
                                                    "floating point number field class");
                ret = -EPERM;
                goto error;
            }

            byte_order = get_real_byte_order(ctx, right);
            if (byte_order == CTF_BYTE_ORDER_UNKNOWN) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `byte_order` attribute in floating point number field class: "
                    "ret=%d",
                    ret);
                ret = -EINVAL;
                goto error;
            }

            _SET(&set, _FLOAT_BYTE_ORDER_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "exp_dig") == 0) {
            if (_IS_SET(&set, _FLOAT_EXP_DIG_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "exp_dig",
                                                    "floating point number field class");
                ret = -EPERM;
                goto error;
            }

            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `exp_dig` attribute in floating point number field class: "
                    "expecting unsigned constant integer: "
                    "node-type=%d",
                    right->u.unary_expression.type);
                ret = -EINVAL;
                goto error;
            }

            exp_dig = right->u.unary_expression.u.unsigned_constant;
            _SET(&set, _FLOAT_EXP_DIG_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "mant_dig") == 0) {
            if (_IS_SET(&set, _FLOAT_MANT_DIG_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "mant_dig",
                                                    "floating point number field class");
                ret = -EPERM;
                goto error;
            }

            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `mant_dig` attribute in floating point number field class: "
                    "expecting unsigned constant integer: "
                    "node-type=%d",
                    right->u.unary_expression.type);
                ret = -EINVAL;
                goto error;
            }

            mant_dig = right->u.unary_expression.u.unsigned_constant;
            _SET(&set, _FLOAT_MANT_DIG_SET);
        } else if (strcmp(left->u.unary_expression.u.string, "align") == 0) {
            if (_IS_SET(&set, _FLOAT_ALIGN_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "align",
                                                    "floating point number field class");
                ret = -EPERM;
                goto error;
            }

            if (right->u.unary_expression.type != UNARY_UNSIGNED_CONSTANT) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `align` attribute in floating point number field class: "
                    "expecting unsigned constant integer: "
                    "node-type=%d",
                    right->u.unary_expression.type);
                ret = -EINVAL;
                goto error;
            }

            alignment = right->u.unary_expression.u.unsigned_constant;

            if (!is_align_valid(alignment)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `align` attribute in floating point number field class: "
                    "expecting power of two: "
                    "align=%" PRIu64,
                    alignment);
                ret = -EINVAL;
                goto error;
            }

            _SET(&set, _FLOAT_ALIGN_SET);
        } else {
            _BT_COMP_LOGW_NODE(left,
                               "Unknown attribute in floating point number field class: "
                               "attr-name=\"%s\"",
                               left->u.unary_expression.u.string);
        }
    }

    if (!_IS_SET(&set, _FLOAT_MANT_DIG_SET)) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
            "Missing `mant_dig` attribute in floating point number field class.");
        ret = -EPERM;
        goto error;
    }

    if (!_IS_SET(&set, _FLOAT_EXP_DIG_SET)) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
            "Missing `exp_dig` attribute in floating point number field class.");
        ret = -EPERM;
        goto error;
    }

    if (mant_dig != 24 && mant_dig != 53) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE("`mant_dig` attribute: expecting 24 or 53.");
        ret = -EPERM;
        goto error;
    }

    if (mant_dig == 24 && exp_dig != 8) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
            "`exp_dig` attribute: expecting 8 because `mant_dig` is 24.");
        ret = -EPERM;
        goto error;
    }

    if (mant_dig == 53 && exp_dig != 11) {
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
            "`exp_dig` attribute: expecting 11 because `mant_dig` is 53.");
        ret = -EPERM;
        goto error;
    }

    if (!_IS_SET(&set, _INTEGER_ALIGN_SET)) {
        if ((mant_dig + exp_dig) % CHAR_BIT) {
            /* Bit-packed alignment */
            alignment = 1;
        } else {
            /* Byte-packed alignment */
            alignment = CHAR_BIT;
        }
    }

    *float_decl = ctf_field_class_float_create();
    BT_ASSERT(*float_decl);
    (*float_decl)->base.base.alignment = alignment;
    (*float_decl)->base.byte_order = byte_order;
    (*float_decl)->base.size = mant_dig + exp_dig;
    return 0;

error:
    ctf_field_class_destroy(&(*float_decl)->base.base);
    *float_decl = NULL;
    return ret;
}

static int visit_string_decl(struct ctf_visitor_generate_ir *ctx, struct bt_list_head *expressions,
                             struct ctf_field_class_string **string_decl)
{
    int set = 0;
    int ret = 0;
    struct ctf_node *expression;
    enum ctf_encoding encoding = CTF_ENCODING_UTF8;

    *string_decl = NULL;

    bt_list_for_each_entry (expression, expressions, siblings) {
        struct ctf_node *left, *right;

        left = _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.left, struct ctf_node, siblings);
        right =
            _BT_LIST_FIRST_ENTRY(&expression->u.ctf_expression.right, struct ctf_node, siblings);

        if (left->u.unary_expression.type != UNARY_STRING) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(left, "Unexpected unary expression type: type=%d",
                                            left->u.unary_expression.type);
            ret = -EINVAL;
            goto error;
        }

        if (strcmp(left->u.unary_expression.u.string, "encoding") == 0) {
            char *s_right;

            if (_IS_SET(&set, _STRING_ENCODING_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(left, "encoding", "string field class");
                ret = -EPERM;
                goto error;
            }

            if (right->u.unary_expression.type != UNARY_STRING) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right, "Invalid `encoding` attribute in string field class: "
                           "expecting unary string.");
                ret = -EINVAL;
                goto error;
            }

            s_right = ctf_ast_concatenate_unary_strings(&expression->u.ctf_expression.right);
            if (!s_right) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Unexpected unary expression for string field class's `encoding` attribute.");
                ret = -EINVAL;
                goto error;
            }

            if (strcmp(s_right, "UTF8") == 0 || strcmp(s_right, "utf8") == 0 ||
                strcmp(s_right, "utf-8") == 0 || strcmp(s_right, "UTF-8") == 0 ||
                strcmp(s_right, "ASCII") == 0 || strcmp(s_right, "ascii") == 0) {
                encoding = CTF_ENCODING_UTF8;
            } else if (strcmp(s_right, "none") == 0) {
                encoding = CTF_ENCODING_NONE;
            } else {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    right,
                    "Invalid `encoding` attribute in string field class: "
                    "unknown encoding: encoding=\"%s\"",
                    s_right);
                g_free(s_right);
                ret = -EINVAL;
                goto error;
            }

            g_free(s_right);
            _SET(&set, _STRING_ENCODING_SET);
        } else {
            _BT_COMP_LOGW_NODE(left,
                               "Unknown attribute in string field class: "
                               "attr-name=\"%s\"",
                               left->u.unary_expression.u.string);
        }
    }

    *string_decl = ctf_field_class_string_create();
    BT_ASSERT(*string_decl);
    (*string_decl)->encoding = encoding;
    return 0;

error:
    ctf_field_class_destroy(&(*string_decl)->base);
    *string_decl = NULL;
    return ret;
}

static int visit_field_class_specifier_list(struct ctf_visitor_generate_ir *ctx,
                                            struct ctf_node *ts_list, struct ctf_field_class **decl)
{
    int ret = 0;
    struct ctf_node *first, *node;

    *decl = NULL;

    if (ts_list->type != NODE_TYPE_SPECIFIER_LIST) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(ts_list, "Unexpected node type: node-type=%d",
                                        ts_list->type);
        ret = -EINVAL;
        goto error;
    }

    first = _BT_LIST_FIRST_ENTRY(&ts_list->u.field_class_specifier_list.head, struct ctf_node,
                                 siblings);
    if (first->type != NODE_TYPE_SPECIFIER) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(first, "Unexpected node type: node-type=%d", first->type);
        ret = -EINVAL;
        goto error;
    }

    node = first->u.field_class_specifier.node;

    switch (first->u.field_class_specifier.type) {
    case TYPESPEC_INTEGER:
    {
        ctf_field_class_int *int_decl;

        ret = visit_integer_decl(ctx, &node->u.integer.expressions, &int_decl);
        if (ret) {
            BT_ASSERT(!int_decl);
            goto error;
        }

        *decl = &int_decl->base.base;
        break;
    }
    case TYPESPEC_FLOATING_POINT:
    {
        ctf_field_class_float *float_decl;

        ret =
            visit_floating_point_number_decl(ctx, &node->u.floating_point.expressions, &float_decl);
        if (ret) {
            BT_ASSERT(!float_decl);
            goto error;
        }

        *decl = &float_decl->base.base;
        break;
    }
    case TYPESPEC_STRING:
    {
        ctf_field_class_string *string_decl;

        ret = visit_string_decl(ctx, &node->u.string.expressions, &string_decl);
        if (ret) {
            BT_ASSERT(!string_decl);
            goto error;
        }

        *decl = &string_decl->base;
        break;
    }
    case TYPESPEC_STRUCT:
    {
        ctf_field_class_struct *struct_decl;

        ret = visit_struct_decl(ctx, node->u._struct.name, &node->u._struct.declaration_list,
                                node->u._struct.has_body, &node->u._struct.min_align, &struct_decl);
        if (ret) {
            BT_ASSERT(!struct_decl);
            goto error;
        }

        *decl = &struct_decl->base;
        break;
    }
    case TYPESPEC_VARIANT:
    {
        ctf_field_class_variant *variant_decl;

        ret = visit_variant_decl(ctx, node->u.variant.name, node->u.variant.choice,
                                 &node->u.variant.declaration_list, node->u.variant.has_body,
                                 &variant_decl);
        if (ret) {
            BT_ASSERT(!variant_decl);
            goto error;
        }

        *decl = &variant_decl->base;
        break;
    }
    case TYPESPEC_ENUM:
    {
        ctf_field_class_enum *enum_decl;

        ret = visit_enum_decl(ctx, node->u._enum.enum_id, node->u._enum.container_field_class,
                              &node->u._enum.enumerator_list, node->u._enum.has_body, &enum_decl);
        if (ret) {
            BT_ASSERT(!enum_decl);
            goto error;
        }

        *decl = &enum_decl->base.base.base;
        break;
    }
    case TYPESPEC_VOID:
    case TYPESPEC_CHAR:
    case TYPESPEC_SHORT:
    case TYPESPEC_INT:
    case TYPESPEC_LONG:
    case TYPESPEC_FLOAT:
    case TYPESPEC_DOUBLE:
    case TYPESPEC_SIGNED:
    case TYPESPEC_UNSIGNED:
    case TYPESPEC_BOOL:
    case TYPESPEC_COMPLEX:
    case TYPESPEC_IMAGINARY:
    case TYPESPEC_CONST:
    case TYPESPEC_ID_TYPE:
        ret = visit_field_class_specifier(ctx, ts_list, decl);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(first, "Cannot visit field class specifier: ret=%d",
                                            ret);
            BT_ASSERT(!*decl);
            goto error;
        }
        break;
    default:
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(first,
                                        "Unexpected field class specifier type: node-type=%d",
                                        first->u.field_class_specifier.type);
        ret = -EINVAL;
        goto error;
    }

    BT_ASSERT(*decl);
    return 0;

error:
    ctf_field_class_destroy(*decl);
    *decl = NULL;
    return ret;
}

static int visit_event_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node,
                                  struct ctf_event_class *event_class, uint64_t *stream_id,
                                  int *set)
{
    int ret = 0;
    char *left = NULL;

    switch (node->type) {
    case NODE_TYPEDEF:
        ret = visit_field_class_def(ctx, node->u.field_class_def.field_class_specifier_list,
                                    &node->u.field_class_def.field_class_declarators);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot add field class found in event class.");
            goto error;
        }
        break;
    case NODE_TYPEALIAS:
        ret = visit_field_class_alias(ctx, node->u.field_class_alias.target,
                                      node->u.field_class_alias.alias);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                            "Cannot add field class alias found in event class.");
            goto error;
        }
        break;
    case NODE_CTF_EXPRESSION:
    {
        left = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.left);
        if (!left) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot concatenate unary strings.");
            ret = -EINVAL;
            goto error;
        }

        if (strcmp(left, "name") == 0) {
            /* This is already known at this stage */
            if (_IS_SET(set, _EVENT_NAME_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "name", "event class");
                ret = -EPERM;
                goto error;
            }

            _SET(set, _EVENT_NAME_SET);
        } else if (strcmp(left, "id") == 0) {
            int64_t id = -1;

            if (_IS_SET(set, _EVENT_ID_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "id", "event class");
                ret = -EPERM;
                goto error;
            }

            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, (uint64_t *) &id);
            /* Only read "id" if get_unary_unsigned() succeeded. */
            if (ret || (!ret && id < 0)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Unexpected unary expression for event class's `id` attribute.");
                ret = -EINVAL;
                goto error;
            }

            event_class->id = id;
            _SET(set, _EVENT_ID_SET);
        } else if (strcmp(left, "stream_id") == 0) {
            if (_IS_SET(set, _EVENT_STREAM_ID_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "stream_id", "event class");
                ret = -EPERM;
                goto error;
            }

            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, stream_id);

            /*
             * Only read "stream_id" if get_unary_unsigned()
             * succeeded.
             */
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Unexpected unary expression for event class's `stream_id` attribute.");
                ret = -EINVAL;
                goto error;
            }

            _SET(set, _EVENT_STREAM_ID_SET);
        } else if (strcmp(left, "context") == 0) {
            if (_IS_SET(set, _EVENT_CONTEXT_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate `context` entry in event class.");
                ret = -EPERM;
                goto error;
            }

            ret = visit_field_class_specifier_list(
                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
                &event_class->spec_context_fc);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                                "Cannot create event class's context field class.");
                goto error;
            }

            BT_ASSERT(event_class->spec_context_fc);
            _SET(set, _EVENT_CONTEXT_SET);
        } else if (strcmp(left, "fields") == 0) {
            if (_IS_SET(set, _EVENT_FIELDS_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate `fields` entry in event class.");
                ret = -EPERM;
                goto error;
            }

            ret = visit_field_class_specifier_list(
                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
                &event_class->payload_fc);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                                "Cannot create event class's payload field class.");
                goto error;
            }

            BT_ASSERT(event_class->payload_fc);
            _SET(set, _EVENT_FIELDS_SET);
        } else if (strcmp(left, "loglevel") == 0) {
            uint64_t loglevel_value;
            bool is_log_level_known = true;
            bt_event_class_log_level log_level;

            if (_IS_SET(set, _EVENT_LOG_LEVEL_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "loglevel", "event class");
                ret = -EPERM;
                goto error;
            }

            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, &loglevel_value);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Unexpected unary expression for event class's `loglevel` attribute.");
                ret = -EINVAL;
                goto error;
            }

            switch (loglevel_value) {
            case 0:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_EMERGENCY;
                break;
            case 1:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_ALERT;
                break;
            case 2:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_CRITICAL;
                break;
            case 3:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_ERROR;
                break;
            case 4:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_WARNING;
                break;
            case 5:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_NOTICE;
                break;
            case 6:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_INFO;
                break;
            case 7:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_SYSTEM;
                break;
            case 8:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_PROGRAM;
                break;
            case 9:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_PROCESS;
                break;
            case 10:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_MODULE;
                break;
            case 11:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_UNIT;
                break;
            case 12:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_FUNCTION;
                break;
            case 13:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG_LINE;
                break;
            case 14:
                log_level = BT_EVENT_CLASS_LOG_LEVEL_DEBUG;
                break;
            default:
                is_log_level_known = false;
                _BT_COMP_LOGW_NODE(
                    node,
                    "Not setting event class's log level because its value is unknown: "
                    "log-level=%" PRIu64,
                    loglevel_value);
            }

            if (is_log_level_known) {
                ctf_event_class_set_log_level(event_class, log_level);
            }

            _SET(set, _EVENT_LOG_LEVEL_SET);
        } else if (strcmp(left, "model.emf.uri") == 0) {
            char *right;

            if (_IS_SET(set, _EVENT_MODEL_EMF_URI_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "model.emf.uri", "event class");
                ret = -EPERM;
                goto error;
            }

            right = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.right);
            if (!right) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node,
                    "Unexpected unary expression for event class's `model.emf.uri` attribute.");
                ret = -EINVAL;
                goto error;
            }

            if (strlen(right) == 0) {
                _BT_COMP_LOGW_NODE(node, "Not setting event class's EMF URI because it's empty.");
            } else {
                g_string_assign(event_class->emf_uri, right);
            }

            g_free(right);
            _SET(set, _EVENT_MODEL_EMF_URI_SET);
        } else {
            _BT_COMP_LOGW_NODE(node,
                               "Unknown attribute in event class: "
                               "attr-name=\"%s\"",
                               left);
        }

        g_free(left);
        left = NULL;
        break;
    }
    default:
        ret = -EPERM;
        goto error;
    }

    goto end;

error:
    g_free(left);

end:
    return ret;
}

static char *get_event_decl_name(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
{
    char *left = NULL;
    char *name = NULL;
    struct ctf_node *iter;
    struct bt_list_head *decl_list = &node->u.event.declaration_list;

    bt_list_for_each_entry (iter, decl_list, siblings) {
        if (iter->type != NODE_CTF_EXPRESSION) {
            continue;
        }

        left = ctf_ast_concatenate_unary_strings(&iter->u.ctf_expression.left);
        if (!left) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot concatenate unary strings.");
            goto error;
        }

        if (strcmp(left, "name") == 0) {
            name = ctf_ast_concatenate_unary_strings(&iter->u.ctf_expression.right);
            if (!name) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    iter, "Unexpected unary expression for event class's `name` attribute.");
                goto error;
            }
        }

        g_free(left);
        left = NULL;

        if (name) {
            break;
        }
    }

    return name;

error:
    g_free(left);
    return NULL;
}

static int visit_event_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
{
    int ret = 0;
    int set = 0;
    struct ctf_node *iter;
    uint64_t stream_id = 0;
    char *event_name = NULL;
    struct ctf_event_class *event_class = NULL;
    struct ctf_stream_class *stream_class = NULL;
    struct bt_list_head *decl_list = &node->u.event.declaration_list;
    bool pop_scope = false;

    if (node->visited) {
        goto end;
    }

    node->visited = TRUE;
    event_name = get_event_decl_name(ctx, node);
    if (!event_name) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Missing `name` attribute in event class.");
        ret = -EPERM;
        goto error;
    }

    event_class = ctf_event_class_create();
    BT_ASSERT(event_class);
    g_string_assign(event_class->name, event_name);
    _TRY_PUSH_SCOPE_OR_GOTO_ERROR();
    pop_scope = true;

    bt_list_for_each_entry (iter, decl_list, siblings) {
        ret = visit_event_decl_entry(ctx, iter, event_class, &stream_id, &set);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
                                            "Cannot visit event class's entry: "
                                            "ret=%d",
                                            ret);
            goto error;
        }
    }

    if (!_IS_SET(&set, _EVENT_STREAM_ID_SET)) {
        /*
         * Allow missing stream_id if there is only a single
         * stream class.
         */
        switch (ctx->ctf_tc->stream_classes->len) {
        case 0:
            /* Create implicit stream class if there's none */
            stream_id = 0;
            stream_class = ctf_stream_class_create();
            BT_ASSERT(stream_class);
            stream_class->id = stream_id;
            g_ptr_array_add(ctx->ctf_tc->stream_classes, stream_class);
            break;
        case 1:
            /* Single stream class: get its ID */
            stream_class = (ctf_stream_class *) ctx->ctf_tc->stream_classes->pdata[0];
            stream_id = stream_class->id;
            break;
        default:
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Missing `stream_id` attribute in event class.");
            ret = -EPERM;
            goto error;
        }
    }

    /* We have the stream ID now; get the stream class if found */
    if (!stream_class) {
        stream_class = ctf_trace_class_borrow_stream_class_by_id(ctx->ctf_tc, stream_id);
        if (!stream_class) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                            "Cannot find stream class at this point: "
                                            "id=%" PRId64,
                                            stream_id);
            ret = -EINVAL;
            goto error;
        }
    }

    BT_ASSERT(stream_class);

    if (!_IS_SET(&set, _EVENT_ID_SET)) {
        /* Allow only one event without ID per stream */
        if (stream_class->event_classes->len != 0) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Missing `id` attribute in event class.");
            ret = -EPERM;
            goto error;
        }

        /* Automatic ID */
        event_class->id = 0;
    }

    if (ctf_stream_class_borrow_event_class_by_id(stream_class, event_class->id)) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                        "Duplicate event class (same ID) in the same stream class: "
                                        "id=%" PRId64,
                                        event_class->id);
        ret = -EEXIST;
        goto error;
    }

    ctf_stream_class_append_event_class(stream_class, event_class);
    event_class = NULL;
    goto end;

error:
    ctf_event_class_destroy(event_class);
    event_class = NULL;

    if (ret >= 0) {
        ret = -1;
    }

end:
    if (pop_scope) {
        ctx_pop_scope(ctx);
    }

    g_free(event_name);

    return ret;
}

static int auto_map_field_to_trace_clock_class(struct ctf_visitor_generate_ir *ctx,
                                               struct ctf_field_class *fc)
{
    struct ctf_clock_class *clock_class_to_map_to = NULL;
    struct ctf_field_class_int *int_fc = ctf_field_class_as_int(fc);
    int ret = 0;
    uint64_t clock_class_count;

    if (!fc) {
        goto end;
    }

    if (fc->type != CTF_FIELD_CLASS_TYPE_INT && fc->type != CTF_FIELD_CLASS_TYPE_ENUM) {
        goto end;
    }

    if (int_fc->mapped_clock_class) {
        /* Already mapped */
        goto end;
    }

    clock_class_count = ctx->ctf_tc->clock_classes->len;

    switch (clock_class_count) {
    case 0:
        /*
         * No clock class exists in the trace at this point. Create an
         * implicit one at 1 GHz, named `default`, and use this clock
         * class.
         */
        clock_class_to_map_to = ctf_clock_class_create();
        BT_ASSERT(clock_class_to_map_to);
        clock_class_to_map_to->frequency = UINT64_C(1000000000);
        g_string_assign(clock_class_to_map_to->name, "default");
        BT_ASSERT(ret == 0);
        g_ptr_array_add(ctx->ctf_tc->clock_classes, clock_class_to_map_to);
        break;
    case 1:
        /*
         * Only one clock class exists in the trace at this point: use
         * this one.
         */
        clock_class_to_map_to = (ctf_clock_class *) ctx->ctf_tc->clock_classes->pdata[0];
        break;
    default:
        /*
         * Timestamp field not mapped to a clock class and there's more
         * than one clock class in the trace: this is an error.
         */
        _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
            "Timestamp field found with no mapped clock class, "
            "but there's more than one clock class in the trace at this point.");
        ret = -1;
        goto end;
    }

    BT_ASSERT(clock_class_to_map_to);
    int_fc->mapped_clock_class = clock_class_to_map_to;

end:
    return ret;
}

static int auto_map_fields_to_trace_clock_class(struct ctf_visitor_generate_ir *ctx,
                                                struct ctf_field_class *root_fc,
                                                const char *field_name)
{
    int ret = 0;
    uint64_t i, count;
    struct ctf_field_class_struct *struct_fc = (ctf_field_class_struct *) root_fc;
    struct ctf_field_class_variant *var_fc = (ctf_field_class_variant *) root_fc;

    if (!root_fc) {
        goto end;
    }

    if (root_fc->type != CTF_FIELD_CLASS_TYPE_STRUCT &&
        root_fc->type != CTF_FIELD_CLASS_TYPE_VARIANT) {
        goto end;
    }

    if (root_fc->type == CTF_FIELD_CLASS_TYPE_STRUCT) {
        count = struct_fc->members->len;
    } else {
        count = var_fc->options->len;
    }

    for (i = 0; i < count; i++) {
        struct ctf_named_field_class *named_fc = NULL;

        if (root_fc->type == CTF_FIELD_CLASS_TYPE_STRUCT) {
            named_fc = ctf_field_class_struct_borrow_member_by_index(struct_fc, i);
        } else if (root_fc->type == CTF_FIELD_CLASS_TYPE_VARIANT) {
            named_fc = ctf_field_class_variant_borrow_option_by_index(var_fc, i);
        } else {
            bt_common_abort();
        }

        if (strcmp(named_fc->name->str, field_name) == 0) {
            ret = auto_map_field_to_trace_clock_class(ctx, named_fc->fc);
            if (ret) {
                _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                    "Cannot automatically map field to trace's clock class: "
                    "field-name=\"%s\"",
                    field_name);
                goto end;
            }
        }

        ret = auto_map_fields_to_trace_clock_class(ctx, named_fc->fc, field_name);
        if (ret) {
            _BT_COMP_OR_COMP_CLASS_LOGE_APPEND_CAUSE(
                "Cannot automatically map structure or variant field class's fields to trace's clock class: "
                "field-name=\"%s\", root-field-name=\"%s\"",
                field_name, named_fc->name->str);
            goto end;
        }
    }

end:
    return ret;
}

static int visit_stream_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node,
                                   struct ctf_stream_class *stream_class, int *set)
{
    int ret = 0;
    char *left = NULL;

    switch (node->type) {
    case NODE_TYPEDEF:
        ret = visit_field_class_def(ctx, node->u.field_class_def.field_class_specifier_list,
                                    &node->u.field_class_def.field_class_declarators);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot add field class found in stream class.");
            goto error;
        }
        break;
    case NODE_TYPEALIAS:
        ret = visit_field_class_alias(ctx, node->u.field_class_alias.target,
                                      node->u.field_class_alias.alias);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                            "Cannot add field class alias found in stream class.");
            goto error;
        }
        break;
    case NODE_CTF_EXPRESSION:
    {
        left = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.left);
        if (!left) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot concatenate unary strings.");
            ret = -EINVAL;
            goto error;
        }

        if (strcmp(left, "id") == 0) {
            int64_t id;

            if (_IS_SET(set, _STREAM_ID_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "id", "stream declaration");
                ret = -EPERM;
                goto error;
            }

            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, (uint64_t *) &id);

            /* Only read "id" if get_unary_unsigned() succeeded. */
            if (ret || (!ret && id < 0)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Unexpected unary expression for stream class's `id` attribute.");
                ret = -EINVAL;
                goto error;
            }

            if (ctf_trace_class_borrow_stream_class_by_id(ctx->ctf_tc, id)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Duplicate stream class (same ID): id=%" PRId64, id);
                ret = -EEXIST;
                goto error;
            }

            stream_class->id = id;
            _SET(set, _STREAM_ID_SET);
        } else if (strcmp(left, "event.header") == 0) {
            if (_IS_SET(set, _STREAM_EVENT_HEADER_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                                "Duplicate `event.header` entry in stream class.");
                ret = -EPERM;
                goto error;
            }

            ret = visit_field_class_specifier_list(
                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
                &stream_class->event_header_fc);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Cannot create stream class's event header field class.");
                goto error;
            }

            BT_ASSERT(stream_class->event_header_fc);
            ret = auto_map_fields_to_trace_clock_class(ctx, stream_class->event_header_fc,
                                                       "timestamp");
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node,
                    "Cannot automatically map specific event header field class fields named `timestamp` to trace's clock class.");
                goto error;
            }

            _SET(set, _STREAM_EVENT_HEADER_SET);
        } else if (strcmp(left, "event.context") == 0) {
            if (_IS_SET(set, _STREAM_EVENT_CONTEXT_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                                "Duplicate `event.context` entry in stream class.");
                ret = -EPERM;
                goto error;
            }

            ret = visit_field_class_specifier_list(
                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
                &stream_class->event_common_context_fc);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Cannot create stream class's event context field class.");
                goto error;
            }

            BT_ASSERT(stream_class->event_common_context_fc);
            _SET(set, _STREAM_EVENT_CONTEXT_SET);
        } else if (strcmp(left, "packet.context") == 0) {
            if (_IS_SET(set, _STREAM_PACKET_CONTEXT_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Duplicate `packet.context` entry in stream class.");
                ret = -EPERM;
                goto error;
            }

            ret = visit_field_class_specifier_list(
                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
                &stream_class->packet_context_fc);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Cannot create stream class's packet context field class.");
                goto error;
            }

            BT_ASSERT(stream_class->packet_context_fc);
            ret = auto_map_fields_to_trace_clock_class(ctx, stream_class->packet_context_fc,
                                                       "timestamp_begin");
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node,
                    "Cannot automatically map specific packet context field class fields named `timestamp_begin` to trace's clock class.");
                goto error;
            }

            ret = auto_map_fields_to_trace_clock_class(ctx, stream_class->packet_context_fc,
                                                       "timestamp_end");
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node,
                    "Cannot automatically map specific packet context field class fields named `timestamp_end` to trace's clock class.");
                goto error;
            }

            _SET(set, _STREAM_PACKET_CONTEXT_SET);
        } else {
            _BT_COMP_LOGW_NODE(node,
                               "Unknown attribute in stream class: "
                               "attr-name=\"%s\"",
                               left);
        }

        g_free(left);
        left = NULL;
        break;
    }

    default:
        ret = -EPERM;
        goto error;
    }

    return 0;

error:
    g_free(left);
    return ret;
}

static int visit_stream_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
{
    int set = 0;
    int ret = 0;
    struct ctf_node *iter;
    struct ctf_stream_class *stream_class = NULL;
    struct bt_list_head *decl_list = &node->u.stream.declaration_list;

    if (node->visited) {
        goto end;
    }

    node->visited = TRUE;
    stream_class = ctf_stream_class_create();
    BT_ASSERT(stream_class);
    _TRY_PUSH_SCOPE_OR_GOTO_ERROR();

    bt_list_for_each_entry (iter, decl_list, siblings) {
        ret = visit_stream_decl_entry(ctx, iter, stream_class, &set);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
                                            "Cannot visit stream class's entry: "
                                            "ret=%d",
                                            ret);
            ctx_pop_scope(ctx);
            goto error;
        }
    }

    ctx_pop_scope(ctx);

    if (_IS_SET(&set, _STREAM_ID_SET)) {
        /* Check that packet header has `stream_id` field */
        struct ctf_named_field_class *named_fc = NULL;

        if (!ctx->ctf_tc->packet_header_fc) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Stream class has a `id` attribute, "
                                                  "but trace has no packet header field class.");
            ret = -EINVAL;
            goto error;
        }

        named_fc = ctf_field_class_struct_borrow_member_by_name(
            ctf_field_class_as_struct(ctx->ctf_tc->packet_header_fc), "stream_id");
        if (!named_fc) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                node, "Stream class has a `id` attribute, "
                      "but trace's packet header field class has no `stream_id` field.");
            ret = -EINVAL;
            goto error;
        }

        if (named_fc->fc->type != CTF_FIELD_CLASS_TYPE_INT &&
            named_fc->fc->type != CTF_FIELD_CLASS_TYPE_ENUM) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                node,
                "Stream class has a `id` attribute, "
                "but trace's packet header field class's `stream_id` field is not an integer field class.");
            ret = -EINVAL;
            goto error;
        }
    } else {
        /* Allow only _one_ ID-less stream */
        if (ctx->ctf_tc->stream_classes->len != 0) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                node,
                "Missing `id` attribute in stream class as there's more than one stream class in the trace.");
            ret = -EPERM;
            goto error;
        }

        /* Automatic ID: 0 */
        stream_class->id = 0;
    }

    /*
     * Make sure that this stream class's ID is currently unique in
     * the trace.
     */
    if (ctf_trace_class_borrow_stream_class_by_id(ctx->ctf_tc, stream_class->id)) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate stream class (same ID): id=%" PRId64,
                                        stream_class->id);
        ret = -EINVAL;
        goto error;
    }

    g_ptr_array_add(ctx->ctf_tc->stream_classes, stream_class);
    stream_class = NULL;
    goto end;

error:
    ctf_stream_class_destroy(stream_class);
    stream_class = NULL;

end:
    return ret;
}

static int visit_trace_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node,
                                  int *set)
{
    int ret = 0;
    char *left = NULL;
    uint64_t val;

    switch (node->type) {
    case NODE_TYPEDEF:
        ret = visit_field_class_def(ctx, node->u.field_class_def.field_class_specifier_list,
                                    &node->u.field_class_def.field_class_declarators);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                node, "Cannot add field class found in trace (`trace` block).");
            goto error;
        }
        break;
    case NODE_TYPEALIAS:
        ret = visit_field_class_alias(ctx, node->u.field_class_alias.target,
                                      node->u.field_class_alias.alias);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                node, "Cannot add field class alias found in trace (`trace` block).");
            goto error;
        }
        break;
    case NODE_CTF_EXPRESSION:
    {
        left = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.left);
        if (!left) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot concatenate unary strings.");
            ret = -EINVAL;
            goto error;
        }

        if (strcmp(left, "major") == 0) {
            if (_IS_SET(set, _TRACE_MAJOR_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "major", "trace");
                ret = -EPERM;
                goto error;
            }

            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, &val);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Unexpected unary expression for trace's `major` attribute.");
                ret = -EINVAL;
                goto error;
            }

            if (val != 1) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                                "Invalid trace's `minor` attribute: expecting 1.");
                ret = -EINVAL;
                goto error;
            }

            ctx->ctf_tc->major = val;
            _SET(set, _TRACE_MAJOR_SET);
        } else if (strcmp(left, "minor") == 0) {
            if (_IS_SET(set, _TRACE_MINOR_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "minor", "trace");
                ret = -EPERM;
                goto error;
            }

            ret = get_unary_unsigned(ctx, &node->u.ctf_expression.right, &val);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    node, "Unexpected unary expression for trace's `minor` attribute.");
                ret = -EINVAL;
                goto error;
            }

            if (val != 8) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                                "Invalid trace's `minor` attribute: expecting 8.");
                ret = -EINVAL;
                goto error;
            }

            ctx->ctf_tc->minor = val;
            _SET(set, _TRACE_MINOR_SET);
        } else if (strcmp(left, "uuid") == 0) {
            if (_IS_SET(set, _TRACE_UUID_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "uuid", "trace");
                ret = -EPERM;
                goto error;
            }

            ret = get_unary_uuid(ctx, &node->u.ctf_expression.right, ctx->ctf_tc->uuid);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Invalid trace's `uuid` attribute.");
                goto error;
            }

            ctx->ctf_tc->is_uuid_set = true;
            _SET(set, _TRACE_UUID_SET);
        } else if (strcmp(left, "byte_order") == 0) {
            /* Default byte order is already known at this stage */
            if (_IS_SET(set, _TRACE_BYTE_ORDER_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "byte_order", "trace");
                ret = -EPERM;
                goto error;
            }

            BT_ASSERT(ctx->ctf_tc->default_byte_order != CTF_BYTE_ORDER_UNKNOWN);
            _SET(set, _TRACE_BYTE_ORDER_SET);
        } else if (strcmp(left, "packet.header") == 0) {
            if (_IS_SET(set, _TRACE_PACKET_HEADER_SET)) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate `packet.header` entry in trace.");
                ret = -EPERM;
                goto error;
            }

            ret = visit_field_class_specifier_list(
                ctx, _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings),
                &ctx->ctf_tc->packet_header_fc);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                                "Cannot create trace's packet header field class.");
                goto error;
            }

            BT_ASSERT(ctx->ctf_tc->packet_header_fc);
            _SET(set, _TRACE_PACKET_HEADER_SET);
        } else {
            _BT_COMP_LOGW_NODE(node,
                               "Unknown attribute in stream class: "
                               "attr-name=\"%s\"",
                               left);
        }

        g_free(left);
        left = NULL;
        break;
    }
    default:
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Unknown expression in trace.");
        ret = -EINVAL;
        goto error;
    }

    return 0;

error:
    g_free(left);
    return ret;
}

static int visit_trace_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
{
    int ret = 0;
    int set = 0;
    struct ctf_node *iter;
    struct bt_list_head *decl_list = &node->u.trace.declaration_list;

    if (node->visited) {
        goto end;
    }

    node->visited = TRUE;

    if (ctx->is_trace_visited) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate trace (`trace` block).");
        ret = -EEXIST;
        goto error;
    }

    _TRY_PUSH_SCOPE_OR_GOTO_ERROR();

    bt_list_for_each_entry (iter, decl_list, siblings) {
        ret = visit_trace_decl_entry(ctx, iter, &set);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
                                            "Cannot visit trace's entry (`trace` block): "
                                            "ret=%d",
                                            ret);
            ctx_pop_scope(ctx);
            goto error;
        }
    }

    ctx_pop_scope(ctx);

    if (!_IS_SET(&set, _TRACE_MAJOR_SET)) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                        "Missing `major` attribute in trace (`trace` block).");
        ret = -EPERM;
        goto error;
    }

    if (!_IS_SET(&set, _TRACE_MINOR_SET)) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                        "Missing `minor` attribute in trace (`trace` block).");
        ret = -EPERM;
        goto error;
    }

    if (!_IS_SET(&set, _TRACE_BYTE_ORDER_SET)) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                        "Missing `byte_order` attribute in trace (`trace` block).");
        ret = -EPERM;
        goto error;
    }

    ctx->is_trace_visited = true;

end:
    return 0;

error:
    return ret;
}

static int visit_env(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
{
    int ret = 0;
    char *left = NULL;
    struct ctf_node *entry_node;
    struct bt_list_head *decl_list = &node->u.env.declaration_list;

    if (node->visited) {
        goto end;
    }

    node->visited = TRUE;

    bt_list_for_each_entry (entry_node, decl_list, siblings) {
        struct bt_list_head *right_head = &entry_node->u.ctf_expression.right;

        if (entry_node->type != NODE_CTF_EXPRESSION) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node,
                                            "Wrong expression in environment entry: "
                                            "node-type=%d",
                                            entry_node->type);
            ret = -EPERM;
            goto error;
        }

        left = ctf_ast_concatenate_unary_strings(&entry_node->u.ctf_expression.left);
        if (!left) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Cannot get environment entry's name.");
            ret = -EINVAL;
            goto error;
        }

        if (is_unary_string(right_head)) {
            char *right = ctf_ast_concatenate_unary_strings(right_head);

            if (!right) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    entry_node,
                    "Unexpected unary expression for environment entry's value: "
                    "name=\"%s\"",
                    left);
                ret = -EINVAL;
                goto error;
            }

            if (strcmp(left, "tracer_name") == 0) {
                if (strncmp(right, "lttng", 5) == 0) {
                    BT_COMP_LOGI("Detected LTTng trace from `%s` environment value: "
                                 "tracer-name=\"%s\"",
                                 left, right);
                    ctx->is_lttng = true;
                }
            }

            ctf_trace_class_append_env_entry(ctx->ctf_tc, left, CTF_TRACE_CLASS_ENV_ENTRY_TYPE_STR,
                                             right, 0);
            g_free(right);
        } else if (is_unary_unsigned(right_head) || is_unary_signed(right_head)) {
            int64_t v;

            if (is_unary_unsigned(right_head)) {
                ret = get_unary_unsigned(ctx, right_head, (uint64_t *) &v);
            } else {
                ret = get_unary_signed(right_head, &v);
            }
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    entry_node,
                    "Unexpected unary expression for environment entry's value: "
                    "name=\"%s\"",
                    left);
                ret = -EINVAL;
                goto error;
            }

            ctf_trace_class_append_env_entry(ctx->ctf_tc, left, CTF_TRACE_CLASS_ENV_ENTRY_TYPE_INT,
                                             NULL, v);
        } else {
            _BT_COMP_LOGW_NODE(entry_node,
                               "Environment entry has unknown type: "
                               "name=\"%s\"",
                               left);
        }

        g_free(left);
        left = NULL;
    }

end:
    return 0;

error:
    g_free(left);
    return ret;
}

static int set_trace_byte_order(struct ctf_visitor_generate_ir *ctx, struct ctf_node *trace_node)
{
    int ret = 0;
    int set = 0;
    char *left = NULL;
    struct ctf_node *node;
    struct bt_list_head *decl_list = &trace_node->u.trace.declaration_list;

    bt_list_for_each_entry (node, decl_list, siblings) {
        if (node->type == NODE_CTF_EXPRESSION) {
            struct ctf_node *right_node;

            left = ctf_ast_concatenate_unary_strings(&node->u.ctf_expression.left);
            if (!left) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Cannot concatenate unary strings.");
                ret = -EINVAL;
                goto error;
            }

            if (strcmp(left, "byte_order") == 0) {
                enum ctf_byte_order bo;

                if (_IS_SET(&set, _TRACE_BYTE_ORDER_SET)) {
                    _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(node, "byte_order", "trace");
                    ret = -EPERM;
                    goto error;
                }

                _SET(&set, _TRACE_BYTE_ORDER_SET);
                right_node =
                    _BT_LIST_FIRST_ENTRY(&node->u.ctf_expression.right, struct ctf_node, siblings);
                bo = byte_order_from_unary_expr(ctx, right_node);
                if (bo == CTF_BYTE_ORDER_UNKNOWN) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                        node, "Invalid `byte_order` attribute in trace (`trace` block): "
                              "expecting `le`, `be`, or `network`.");
                    ret = -EINVAL;
                    goto error;
                } else if (bo == CTF_BYTE_ORDER_DEFAULT) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                        node, "Invalid `byte_order` attribute in trace (`trace` block): "
                              "cannot be set to `native` here.");
                    ret = -EPERM;
                    goto error;
                }

                ctx->ctf_tc->default_byte_order = bo;
            }

            g_free(left);
            left = NULL;
        }
    }

    if (!_IS_SET(&set, _TRACE_BYTE_ORDER_SET)) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(trace_node,
                                        "Missing `byte_order` attribute in trace (`trace` block).");
        ret = -EINVAL;
        goto error;
    }

    return 0;

error:
    g_free(left);
    return ret;
}

static int visit_clock_decl_entry(struct ctf_visitor_generate_ir *ctx, struct ctf_node *entry_node,
                                  struct ctf_clock_class *clock, int *set, int64_t *offset_seconds,
                                  uint64_t *offset_cycles)
{
    int ret = 0;
    char *left = NULL;

    if (entry_node->type != NODE_CTF_EXPRESSION) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Unexpected node type: node-type=%d",
                                        entry_node->type);
        ret = -EPERM;
        goto error;
    }

    left = ctf_ast_concatenate_unary_strings(&entry_node->u.ctf_expression.left);
    if (!left) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Cannot concatenate unary strings.");
        ret = -EINVAL;
        goto error;
    }

    if (strcmp(left, "name") == 0) {
        char *right;

        if (_IS_SET(set, _CLOCK_NAME_SET)) {
            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "name", "clock class");
            ret = -EPERM;
            goto error;
        }

        right = ctf_ast_concatenate_unary_strings(&entry_node->u.ctf_expression.right);
        if (!right) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Unexpected unary expression for clock class's `name` attribute.");
            ret = -EINVAL;
            goto error;
        }

        g_string_assign(clock->name, right);
        g_free(right);
        _SET(set, _CLOCK_NAME_SET);
    } else if (strcmp(left, "uuid") == 0) {
        bt_uuid_t uuid;

        if (_IS_SET(set, _CLOCK_UUID_SET)) {
            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "uuid", "clock class");
            ret = -EPERM;
            goto error;
        }

        ret = get_unary_uuid(ctx, &entry_node->u.ctf_expression.right, uuid);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Invalid clock class's `uuid` attribute.");
            goto error;
        }

        clock->has_uuid = true;
        bt_uuid_copy(clock->uuid, uuid);
        _SET(set, _CLOCK_UUID_SET);
    } else if (strcmp(left, "description") == 0) {
        char *right;

        if (_IS_SET(set, _CLOCK_DESCRIPTION_SET)) {
            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "description", "clock class");
            ret = -EPERM;
            goto error;
        }

        right = ctf_ast_concatenate_unary_strings(&entry_node->u.ctf_expression.right);
        if (!right) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node,
                "Unexpected unary expression for clock class's `description` attribute.");
            ret = -EINVAL;
            goto error;
        }

        g_string_assign(clock->description, right);
        g_free(right);
        _SET(set, _CLOCK_DESCRIPTION_SET);
    } else if (strcmp(left, "freq") == 0) {
        uint64_t freq = UINT64_C(-1);

        if (_IS_SET(set, _CLOCK_FREQ_SET)) {
            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "freq", "clock class");
            ret = -EPERM;
            goto error;
        }

        ret = get_unary_unsigned(ctx, &entry_node->u.ctf_expression.right, &freq);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Unexpected unary expression for clock class's `freq` attribute.");
            ret = -EINVAL;
            goto error;
        }

        if (freq == UINT64_C(-1) || freq == 0) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node,
                                            "Invalid clock class frequency: freq=%" PRIu64, freq);
            ret = -EINVAL;
            goto error;
        }

        clock->frequency = freq;
        _SET(set, _CLOCK_FREQ_SET);
    } else if (strcmp(left, "precision") == 0) {
        uint64_t precision;

        if (_IS_SET(set, _CLOCK_PRECISION_SET)) {
            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "precision", "clock class");
            ret = -EPERM;
            goto error;
        }

        ret = get_unary_unsigned(ctx, &entry_node->u.ctf_expression.right, &precision);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Unexpected unary expression for clock class's `precision` attribute.");
            ret = -EINVAL;
            goto error;
        }

        clock->precision = precision;
        _SET(set, _CLOCK_PRECISION_SET);
    } else if (strcmp(left, "offset_s") == 0) {
        if (_IS_SET(set, _CLOCK_OFFSET_S_SET)) {
            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "offset_s", "clock class");
            ret = -EPERM;
            goto error;
        }

        ret = get_unary_signed(&entry_node->u.ctf_expression.right, offset_seconds);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Unexpected unary expression for clock class's `offset_s` attribute.");
            ret = -EINVAL;
            goto error;
        }

        _SET(set, _CLOCK_OFFSET_S_SET);
    } else if (strcmp(left, "offset") == 0) {
        if (_IS_SET(set, _CLOCK_OFFSET_SET)) {
            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "offset", "clock class");
            ret = -EPERM;
            goto error;
        }

        ret = get_unary_unsigned(ctx, &entry_node->u.ctf_expression.right, offset_cycles);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Unexpected unary expression for clock class's `offset` attribute.");
            ret = -EINVAL;
            goto error;
        }

        _SET(set, _CLOCK_OFFSET_SET);
    } else if (strcmp(left, "absolute") == 0) {
        struct ctf_node *right;

        if (_IS_SET(set, _CLOCK_ABSOLUTE_SET)) {
            _BT_COMP_LOGE_APPEND_CAUSE_DUP_ATTR(entry_node, "absolute", "clock class");
            ret = -EPERM;
            goto error;
        }

        right =
            _BT_LIST_FIRST_ENTRY(&entry_node->u.ctf_expression.right, struct ctf_node, siblings);
        ret = get_boolean(ctx, right);
        if (ret < 0) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                entry_node, "Unexpected unary expression for clock class's `absolute` attribute.");
            ret = -EINVAL;
            goto error;
        }

        clock->is_absolute = ret;
        _SET(set, _CLOCK_ABSOLUTE_SET);
    } else {
        _BT_COMP_LOGW_NODE(entry_node, "Unknown attribute in clock class: attr-name=\"%s\"", left);
    }

    g_free(left);
    left = NULL;
    return 0;

error:
    g_free(left);
    return ret;
}

static inline uint64_t cycles_from_ns(uint64_t frequency, uint64_t ns)
{
    uint64_t cycles;

    /* 1GHz */
    if (frequency == UINT64_C(1000000000)) {
        cycles = ns;
    } else {
        cycles = (uint64_t) (((double) ns * (double) frequency) / 1e9);
    }

    return cycles;
}

static void calibrate_clock_class_offsets(int64_t *offset_seconds, uint64_t *offset_cycles,
                                          uint64_t freq)
{
    if (*offset_cycles >= freq) {
        const uint64_t s_in_offset_cycles = *offset_cycles / freq;

        *offset_seconds += (int64_t) s_in_offset_cycles;
        *offset_cycles -= (s_in_offset_cycles * freq);
    }
}

static void apply_clock_class_is_absolute(struct ctf_visitor_generate_ir *ctx,
                                          struct ctf_clock_class *clock)
{
    if (ctx->decoder_config.force_clock_class_origin_unix_epoch) {
        clock->is_absolute = true;
    }

    return;
}

static void apply_clock_class_offset(struct ctf_visitor_generate_ir *ctx,
                                     struct ctf_clock_class *clock)
{
    uint64_t freq;
    int64_t offset_s_to_apply = ctx->decoder_config.clock_class_offset_s;
    uint64_t offset_ns_to_apply;
    int64_t cur_offset_s;
    uint64_t cur_offset_cycles;

    if (ctx->decoder_config.clock_class_offset_s == 0 &&
        ctx->decoder_config.clock_class_offset_ns == 0) {
        goto end;
    }

    /* Transfer nanoseconds to seconds as much as possible */
    if (ctx->decoder_config.clock_class_offset_ns < 0) {
        const int64_t abs_ns = -ctx->decoder_config.clock_class_offset_ns;
        const int64_t abs_extra_s = abs_ns / INT64_C(1000000000) + 1;
        const int64_t extra_s = -abs_extra_s;
        const int64_t offset_ns =
            ctx->decoder_config.clock_class_offset_ns - (extra_s * INT64_C(1000000000));

        BT_ASSERT(offset_ns > 0);
        offset_ns_to_apply = (uint64_t) offset_ns;
        offset_s_to_apply += extra_s;
    } else {
        const int64_t extra_s = ctx->decoder_config.clock_class_offset_ns / INT64_C(1000000000);
        const int64_t offset_ns =
            ctx->decoder_config.clock_class_offset_ns - (extra_s * INT64_C(1000000000));

        BT_ASSERT(offset_ns >= 0);
        offset_ns_to_apply = (uint64_t) offset_ns;
        offset_s_to_apply += extra_s;
    }

    freq = clock->frequency;
    cur_offset_s = clock->offset_seconds;
    cur_offset_cycles = clock->offset_cycles;

    /* Apply offsets */
    cur_offset_s += offset_s_to_apply;
    cur_offset_cycles += cycles_from_ns(freq, offset_ns_to_apply);

    /*
     * Recalibrate offsets because the part in cycles can be greater
     * than the frequency at this point.
     */
    calibrate_clock_class_offsets(&cur_offset_s, &cur_offset_cycles, freq);

    /* Set final offsets */
    clock->offset_seconds = cur_offset_s;
    clock->offset_cycles = cur_offset_cycles;

end:
    return;
}

static int visit_clock_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *clock_node)
{
    int ret = 0;
    int set = 0;
    struct ctf_clock_class *clock;
    struct ctf_node *entry_node;
    struct bt_list_head *decl_list = &clock_node->u.clock.declaration_list;
    const char *clock_class_name;
    int64_t offset_seconds = 0;
    uint64_t offset_cycles = 0;
    uint64_t freq;

    if (clock_node->visited) {
        return 0;
    }

    clock_node->visited = TRUE;

    /* CTF 1.8's default frequency for a clock class is 1 GHz */
    clock = ctf_clock_class_create();
    if (!clock) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(clock_node, "Cannot create default clock class.");
        ret = -ENOMEM;
        goto end;
    }

    bt_list_for_each_entry (entry_node, decl_list, siblings) {
        ret = visit_clock_decl_entry(ctx, entry_node, clock, &set, &offset_seconds, &offset_cycles);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(entry_node, "Cannot visit clock class's entry: ret=%d",
                                            ret);
            goto end;
        }
    }

    if (!_IS_SET(&set, _CLOCK_NAME_SET)) {
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(clock_node, "Missing `name` attribute in clock class.");
        ret = -EPERM;
        goto end;
    }

    clock_class_name = clock->name->str;
    BT_ASSERT(clock_class_name);
    if (ctx->is_lttng && strcmp(clock_class_name, "monotonic") == 0) {
        /*
         * Old versions of LTTng forgot to set its clock class
         * as absolute, even if it is. This is important because
         * it's a condition to be able to sort messages
         * from different sources.
         */
        clock->is_absolute = true;
    }

    /*
     * Adjust offsets so that the part in cycles is less than the
     * frequency (move to the part in seconds).
     */
    freq = clock->frequency;
    calibrate_clock_class_offsets(&offset_seconds, &offset_cycles, freq);
    BT_ASSERT(offset_cycles < clock->frequency);
    clock->offset_seconds = offset_seconds;
    clock->offset_cycles = offset_cycles;
    apply_clock_class_offset(ctx, clock);
    apply_clock_class_is_absolute(ctx, clock);
    g_ptr_array_add(ctx->ctf_tc->clock_classes, clock);
    clock = NULL;

end:
    if (clock) {
        ctf_clock_class_destroy(clock);
    }

    return ret;
}

static int visit_root_decl(struct ctf_visitor_generate_ir *ctx, struct ctf_node *root_decl_node)
{
    int ret = 0;

    if (root_decl_node->visited) {
        goto end;
    }

    root_decl_node->visited = TRUE;

    switch (root_decl_node->type) {
    case NODE_TYPEDEF:
        ret =
            visit_field_class_def(ctx, root_decl_node->u.field_class_def.field_class_specifier_list,
                                  &root_decl_node->u.field_class_def.field_class_declarators);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(root_decl_node,
                                            "Cannot add field class found in root scope.");
            goto end;
        }
        break;
    case NODE_TYPEALIAS:
        ret = visit_field_class_alias(ctx, root_decl_node->u.field_class_alias.target,
                                      root_decl_node->u.field_class_alias.alias);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(root_decl_node,
                                            "Cannot add field class alias found in root scope.");
            goto end;
        }
        break;
    case NODE_TYPE_SPECIFIER_LIST:
    {
        struct ctf_field_class *decl = NULL;

        /*
         * Just add the field class specifier to the root
         * declaration scope. Put local reference.
         */
        ret = visit_field_class_specifier_list(ctx, root_decl_node, &decl);
        if (ret) {
            _BT_COMP_LOGE_APPEND_CAUSE_NODE(root_decl_node,
                                            "Cannot visit root scope's field class: "
                                            "ret=%d",
                                            ret);
            BT_ASSERT(!decl);
            goto end;
        }

        ctf_field_class_destroy(decl);
        decl = NULL;
        break;
    }
    default:
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(root_decl_node, "Unexpected node type: node-type=%d",
                                        root_decl_node->type);
        ret = -EPERM;
        goto end;
    }

end:
    return ret;
}

BT_HIDDEN
struct ctf_visitor_generate_ir *
ctf_visitor_generate_ir_create(const struct ctf_metadata_decoder_config *decoder_config)
{
    struct ctf_visitor_generate_ir *ctx = NULL;

    /* Create visitor's context */
    ctx = ctx_create(decoder_config);
    if (!ctx) {
        BT_COMP_LOG_CUR_LVL(BT_LOG_ERROR, decoder_config->log_level, decoder_config->self_comp,
                            "Cannot create visitor's context.");
        goto error;
    }

    goto end;

error:
    ctx_destroy(ctx);
    ctx = NULL;

end:
    return ctx;
}

BT_HIDDEN
void ctf_visitor_generate_ir_destroy(struct ctf_visitor_generate_ir *visitor)
{
    ctx_destroy(visitor);
}

BT_HIDDEN
bt_trace_class *ctf_visitor_generate_ir_get_ir_trace_class(struct ctf_visitor_generate_ir *ctx)
{
    BT_ASSERT_DBG(ctx);

    if (ctx->trace_class) {
        bt_trace_class_get_ref(ctx->trace_class);
    }

    return ctx->trace_class;
}

BT_HIDDEN
struct ctf_trace_class *
ctf_visitor_generate_ir_borrow_ctf_trace_class(struct ctf_visitor_generate_ir *ctx)
{
    BT_ASSERT_DBG(ctx);
    BT_ASSERT_DBG(ctx->ctf_tc);
    return ctx->ctf_tc;
}

BT_HIDDEN
int ctf_visitor_generate_ir_visit_node(struct ctf_visitor_generate_ir *ctx, struct ctf_node *node)
{
    int ret = 0;

    BT_COMP_LOGI_STR("Visiting metadata's AST to generate CTF IR objects.");

    switch (node->type) {
    case NODE_ROOT:
    {
        struct ctf_node *iter;
        bool got_trace_decl = false;

        /*
         * The first thing we need is the native byte order of
         * the trace block, because early class aliases can have
         * a `byte_order` attribute set to `native`. If we don't
         * have the native byte order yet, and we don't have any
         * trace block yet, then fail with EINCOMPLETE.
         */
        if (ctx->ctf_tc->default_byte_order == CTF_BYTE_ORDER_UNKNOWN) {
            bt_list_for_each_entry (iter, &node->u.root.trace, siblings) {
                if (got_trace_decl) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Duplicate trace (`trace` block).");
                    ret = -1;
                    goto end;
                }

                ret = set_trace_byte_order(ctx, iter);
                if (ret) {
                    _BT_COMP_LOGE_APPEND_CAUSE_NODE(node,
                                                    "Cannot set trace's native byte order: "
                                                    "ret=%d",
                                                    ret);
                    goto end;
                }

                got_trace_decl = true;
            }

            if (!got_trace_decl) {
                BT_COMP_LOGD_STR("Incomplete AST: need trace (`trace` block).");
                ret = -EINCOMPLETE;
                goto end;
            }
        }

        BT_ASSERT(ctx->ctf_tc->default_byte_order == CTF_BYTE_ORDER_LITTLE ||
                  ctx->ctf_tc->default_byte_order == CTF_BYTE_ORDER_BIG);
        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);

        /* Environment */
        bt_list_for_each_entry (iter, &node->u.root.env, siblings) {
            ret = visit_env(ctx, iter);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(
                    iter,
                    "Cannot visit trace's environment (`env` block) entry: "
                    "ret=%d",
                    ret);
                goto end;
            }
        }

        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);

        /*
         * Visit clock blocks.
         */
        bt_list_for_each_entry (iter, &node->u.root.clock, siblings) {
            ret = visit_clock_decl(ctx, iter);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit clock class: ret=%d", ret);
                goto end;
            }
        }

        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);

        /*
         * Visit root declarations next, as they can be used by any
         * following entity.
         */
        bt_list_for_each_entry (iter, &node->u.root.declaration_list, siblings) {
            ret = visit_root_decl(ctx, iter);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit root entry: ret=%d", ret);
                goto end;
            }
        }

        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);

        /* Callsite blocks are not supported */
        bt_list_for_each_entry (iter, &node->u.root.callsite, siblings) {
            _BT_COMP_LOGW_NODE(iter, "\"callsite\" blocks are not supported as of this version.");
        }

        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);

        /* Trace */
        bt_list_for_each_entry (iter, &node->u.root.trace, siblings) {
            ret = visit_trace_decl(ctx, iter);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter,
                                                "Cannot visit trace (`trace` block): "
                                                "ret=%d",
                                                ret);
                goto end;
            }
        }

        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);

        /* Streams */
        bt_list_for_each_entry (iter, &node->u.root.stream, siblings) {
            ret = visit_stream_decl(ctx, iter);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit stream class: ret=%d", ret);
                goto end;
            }
        }

        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);

        /* Events */
        bt_list_for_each_entry (iter, &node->u.root.event, siblings) {
            ret = visit_event_decl(ctx, iter);
            if (ret) {
                _BT_COMP_LOGE_APPEND_CAUSE_NODE(iter, "Cannot visit event class: ret=%d", ret);
                goto end;
            }
        }

        BT_ASSERT(ctx->current_scope && !ctx->current_scope->parent_scope);
        break;
    }
    default:
        _BT_COMP_LOGE_APPEND_CAUSE_NODE(node, "Unexpected node type: node-type=%d", node->type);
        ret = -EINVAL;
        goto end;
    }

    /* Update default clock classes */
    ret = ctf_trace_class_update_default_clock_classes(ctx->ctf_tc, &ctx->log_cfg);
    if (ret) {
        ret = -EINVAL;
        goto end;
    }

    /* Update trace class meanings */
    ret = ctf_trace_class_update_meanings(ctx->ctf_tc);
    if (ret) {
        ret = -EINVAL;
        goto end;
    }

    /* Update stream class configuration */
    ret = ctf_trace_class_update_stream_class_config(ctx->ctf_tc);
    if (ret) {
        ret = -EINVAL;
        goto end;
    }

    /* Update text arrays and sequences */
    ret = ctf_trace_class_update_text_array_sequence(ctx->ctf_tc);
    if (ret) {
        ret = -EINVAL;
        goto end;
    }

    /* Update structure/array/sequence alignments */
    ret = ctf_trace_class_update_alignments(ctx->ctf_tc);
    if (ret) {
        ret = -EINVAL;
        goto end;
    }

    /* Resolve sequence lengths and variant tags */
    ret = ctf_trace_class_resolve_field_classes(ctx->ctf_tc, &ctx->log_cfg);
    if (ret) {
        ret = -EINVAL;
        goto end;
    }

    if (ctx->trace_class) {
        /*
         * Update "in IR" for field classes.
         *
         * If we have no IR trace class, then we'll have no way
         * to create IR fields anyway, so we leave all the
         * `in_ir` members false.
         */
        ret = ctf_trace_class_update_in_ir(ctx->ctf_tc);
        if (ret) {
            ret = -EINVAL;
            goto end;
        }
    }

    /* Update saved value indexes */
    ret = ctf_trace_class_update_value_storing_indexes(ctx->ctf_tc);
    if (ret) {
        ret = -EINVAL;
        goto end;
    }

    /* Validate what we have so far */
    ret = ctf_trace_class_validate(ctx->ctf_tc, &ctx->log_cfg);
    if (ret) {
        ret = -EINVAL;
        goto end;
    }

    /*
     * If there are fields which are not related to the CTF format
     * itself in the packet header and in event header field
     * classes, warn about it because they are never translated.
     */
    ctf_trace_class_warn_meaningless_header_fields(ctx->ctf_tc, &ctx->log_cfg);

    if (ctx->trace_class) {
        /* Copy new CTF metadata -> new IR metadata */
        ret = ctf_trace_class_translate(ctx->log_cfg.self_comp, ctx->trace_class, ctx->ctf_tc);
        if (ret) {
            ret = -EINVAL;
            goto end;
        }
    }

end:
    return ret;
}
